Compare commits

..

454 Commits
0.4.2 ... 1.1.0

Author SHA1 Message Date
Ben Firshman
03535a6158 Merge pull request #1027 from aanand/ship-1.1.0
Ship 1.1.0
2015-02-25 18:42:28 +00:00
Aanand Prasad
4ac02bfca6 Ship 1.1.0
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-25 18:16:44 +00:00
Aanand Prasad
72003de737 Merge pull request #1020 from bfirsh/move-docs-index-higher-up-on-compose-docs
Move docs index higher up on index page
2015-02-25 17:52:56 +00:00
Ben Firshman
a0db5bee3e Merge pull request #1022 from aanand/previously-known-as
Add 'previously known as Fig' note to README.md
2015-02-25 17:52:04 +00:00
Ben Firshman
59c45e3398 Merge pull request #1026 from aanand/fix-requests-version-range
Fix requests version range
2015-02-25 17:51:39 +00:00
Aanand Prasad
ea8364fd11 Add 'previously known as Fig' note to README.md
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-25 17:39:12 +00:00
Aanand Prasad
cf6b09e94b Fix requests version range
It was more permissive than docker-py's, resulting in an incompatible
version (2.5.x) being installed.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-25 17:34:22 +00:00
Ben Firshman
66555aa69b Merge pull request #1021 from aanand/update-urls
Update URLs in documentation
2015-02-25 16:48:28 +00:00
Aanand Prasad
bb943d5cb5 Update URLs in documentation
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-25 14:58:03 +00:00
Ben Firshman
178c50d46f Move docs index higher up on index page
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2015-02-25 14:09:30 +00:00
Ben Firshman
32cdabefc2 Merge pull request #1016 from SvenDowideit/docs-tweak
Add an index to the bottom of the Compose docs as they're scattered arou...
2015-02-25 13:55:47 +00:00
Sven Dowideit
5b07c581e0 Add an index to the bottom of the Compose docs as they're scattered around docs.docker.com
Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
2015-02-25 23:38:25 +10:00
Ben Firshman
17bbb9d357 Merge pull request #1017 from aanand/swarm-doc
Document Swarm integration
2015-02-25 12:53:07 +00:00
Aanand Prasad
ec2966222a Document Swarm integration
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-25 10:45:40 +00:00
Aanand Prasad
8076c7d7fe Merge pull request #1010 from bfirsh/add-create-message-when-scaling-containers
Log "creating container" when scaling
2015-02-24 14:09:12 +00:00
Ben Firshman
b2425c1f1e Log "creating container" when scaling
If an image needs pulling, it just looks like it's hanging.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2015-02-24 13:39:38 +00:00
Ben Firshman
8d8dd37d2c Merge pull request #924 from aanand/update-contributing
Add checklist item for completion script URL to CONTRIBUTING.md
2015-02-24 10:44:16 +00:00
Ben Firshman
b1739e703b Merge pull request #1002 from SvenDowideit/add-links-to-references
Add links to the main Compose references
2015-02-24 10:43:34 +00:00
Ben Firshman
a42f2007ea Merge pull request #1001 from SvenDowideit/build-docs-in-local-fig-repo
add ./script/doc to build fig documentation using the docs.docker.com to...
2015-02-23 22:45:59 +00:00
Aanand Prasad
1dd5ef4133 Merge pull request #619 from bfirsh/add-script-which-runs-fig-inside-docker
Add script which runs Fig inside Docker
2015-02-23 18:20:33 +00:00
Sven Dowideit
c3215a1764 Add links to the main Compose references
Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
2015-02-23 16:13:58 +10:00
Sven Dowideit
bd320b19fe add ./script/doc to build fig documentation using the docs.docker.com tooling
Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
2015-02-23 13:56:13 +10:00
Ben Firshman
0fa0131372 Add script which runs Fig inside Docker
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2015-02-20 14:33:15 +00:00
Aanand Prasad
a7fe67e691 Merge pull request #991 from aanand/credit-contributors
Credit contributors in CHANGES.md
2015-02-19 18:08:45 +00:00
Aanand Prasad
f78dfa7958 Credit contributors in CHANGES.md
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-19 17:42:33 +00:00
Aanand Prasad
d95de03de9 Merge pull request #990 from bfirsh/add-title-to-docs-index
Add title to docs index
2015-02-19 16:02:22 +00:00
Ben Firshman
e3eccd1047 Merge pull request #979 from aanand/fix-env-docs
Update environment variable names in docs
2015-02-19 14:34:48 +00:00
Ben Firshman
d32994c250 Add title to docs index
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2015-02-19 14:34:08 +00:00
Aanand Prasad
a516d61b49 Update environment variable names in docs
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-18 11:39:57 +00:00
Aanand Prasad
6eb1c8896f Merge pull request #952 from fredlf/cli-doc-revision
Revises Compose cli reference
2015-02-18 11:38:50 +00:00
Ben Firshman
160e2dc7b9 Merge pull request #949 from aanand/update-intro
Rejig introduction
2015-02-18 09:46:16 +00:00
Aanand Prasad
5e8bcd2d29 Rejig introduction
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-17 10:58:51 +00:00
Ben Firshman
2eb20d89af Merge pull request #904 from fredlf/install-doc-revision
Revision and edit of Compose install doc
2015-02-13 12:25:32 +00:00
Aanand Prasad
ab65c829ea Merge pull request #960 from bfirsh/remove-nathanleclaire-as-maintainer
Remove @nathanleclaire as a maintainer
2015-02-13 11:59:27 +00:00
Aanand Prasad
83110b8e6b Merge pull request #959 from bfirsh/tidy-up-contributing-guidelines
Tidy up contributing guidelines
2015-02-13 11:43:05 +00:00
Ben Firshman
0a8f9abfae Remove @nathanleclaire as a maintainer
Thanks for your help <3

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2015-02-12 16:39:59 +00:00
Ben Firshman
3146fe5e4b Tidy up contributing guidelines
- Add intro saying we roughly follow the Docker project's guidelines
- Link to Docker's signing off docs
- Fix formatting at bottom

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2015-02-12 13:40:02 +00:00
Fred Lifton
4a33686787 Revises Compose cli reference 2015-02-11 18:22:36 -08:00
Aanand Prasad
64239f1408 Merge pull request #950 from aanand/update-pep8
Update pep8, fix errors and freeze requirements-dev.txt
2015-02-11 21:33:14 +00:00
Aanand Prasad
c39a0b0a2d Update pep8, fix errors and freeze requirements-dev.txt
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-11 19:00:13 +00:00
Fred Lifton
8a7b3fb0eb Merge pull request #922 from aanand/tweak-intro
Tweak intro
2015-02-09 16:11:21 -08:00
Daniel Nephin
4251f2b732 Merge pull request #911 from ggtools/release_notes
Add missing cpu_shares option for rel. 1.1.0-rc1
2015-02-06 10:47:40 -05:00
Fred Lifton
9f4775c554 Revision and edit of Compose install doc
Fix rc version to latest.

Signed-off-by: Fred Lifton <fred.lifton@docker.com>
2015-02-05 17:48:06 -08:00
Aanand Prasad
7ff607fb7a Add checklist item for completion script URL to CONTRIBUTING.md
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-05 17:37:04 -05:00
Aanand Prasad
b25ed59b1c Tweak intro
We shouldn't yet be recommending production use - for now, let's continue to emphasise the development use case and mention staging/CI.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-02-05 16:17:38 -05:00
Daniel Nephin
eabca3d7b7 Merge pull request #907 from apenney/typo
Fix a small typo.
2015-02-02 11:02:12 -05:00
Christophe Labouisse
45b8d526ba Add missing cpu_shares option for rel. 1.1.0-rc1
Signed-off-by: Christophe Labouisse <christophe@labouisse.org>
2015-02-01 17:03:50 +01:00
Ashley Penney
75247e5a54 Fix a small typo. 2015-01-31 20:00:04 -05:00
Fred Lifton
2cf1fa6c9d Merge pull request #902 from SvenDowideit/iterate-on-freds-index-doc-revisions
Edit and revision of overview & quick start doc
2015-01-30 10:57:19 -08:00
Sven Dowideit
3b7ea5c055 resolve most of my comments
Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
2015-01-30 21:27:57 +10:00
Fred Lifton
461f1ad5d5 Edit and revision of overview & quick start doc
Signed-off-by: Fred Lifton <fred.lifton@docker.com>
2015-01-29 19:55:11 -08:00
Fred Lifton
2307adc4b2 Merge pull request #855 from SvenDowideit/add-docker-docs-metadata-and-reflow-to-80-chars
Add Docker docs.docker.com meta-data, and reflow to 80-chars to simplify GH diffs
2015-01-29 19:21:23 -08:00
Sven Dowideit
d8d0fd6dc9 Add Docker docs.docker.com meta-data, and reflow to 80-chars to simplify github diffs
Signed-off-by: Sven Dowideit <SvenDowideit@docker.com>
2015-01-30 13:11:48 +10:00
Aanand Prasad
e4d6a2c240 Merge pull request #900 from aanand/ship-1.1.0-rc2
Ship 1.1.0-rc2
2015-01-29 18:00:26 -05:00
Aanand Prasad
deb2de3c07 Ship 1.1.0-rc2
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-29 18:00:10 -05:00
Aanand Prasad
bc1f6c97d8 Merge pull request #898 from aanand/support-fig-yml
Support fig.yml
2015-01-29 13:47:49 -05:00
Aanand Prasad
7c087f1c07 Support fig.y(a)ml and show a deprecation warning
Closes #864

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-29 13:14:44 -05:00
Ben Firshman
fd30920aac Merge pull request #897 from aanand/nicer-env-file-error
Nicer env file error
2015-01-29 00:51:05 +00:00
Aanand Prasad
9bc7604e0e Make sure we're testing uppercase directories properly
(OS X is case-sensitive so we can't have fixture dirs which are
identically named if you ignore case)

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-28 17:19:27 -05:00
Aanand Prasad
de07e0471e Show a nicer error when the env file doesn't exist
Closes #865

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-28 16:20:01 -05:00
Aanand Prasad
dfc6206d0d Extract get_env_files()
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-28 16:18:02 -05:00
Aanand Prasad
6c45b6ccdb Make sure we're testing blank lines and comments in env files
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-28 16:18:02 -05:00
Daniel Nephin
d79dc85fa5 Merge pull request #887 from albers/multiple-configfiles
Bash completion supports fig.yml and other legacy filenames
2015-01-27 23:08:23 -05:00
Aanand Prasad
22aca1a8a5 Merge pull request #879 from albers/bash-completion
Update bash completion for compose 1.1.0-rc1
2015-01-27 16:34:33 -05:00
Aanand Prasad
74302560f5 Merge pull request #891 from aanand/fix-image-volumes-test
Fix test for image-declared volumes
2015-01-27 16:33:59 -05:00
Aanand Prasad
bc2f6044fd Merge pull request #880 from dnephin/use_latest_docker_py
Use latest docker-py
2015-01-27 16:32:56 -05:00
Aanand Prasad
1476027410 Fix test for image-declared volumes
A stray 'fig' got lost in the merge post-rename, meaning the containers
weren't being cleaned up properly.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-27 14:46:58 -05:00
Daniel Nephin
28fa49e569 Merge pull request #810 from madwire/patch-1
Tweaks to the rails tutorial to bring it inline with rails 4.2 release
2015-01-27 14:45:31 -05:00
Harald Albers
27e4f982fa Bash completion supports fig.yml and other legacy filenames
Signed-off-by: Harald Albers <github@albersweb.de>
2015-01-27 09:03:04 +01:00
Daniel Nephin
0bc4a28dcc Use latest docker-py.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2015-01-25 15:33:15 -05:00
Harald Albers
bd535c76d0 Add --service-ports to bash completion
Signed-off-by: Harald Albers <github@albersweb.de>
2015-01-25 09:47:30 -08:00
Daniel Nephin
ef027599f7 Merge pull request #871 from albers/bash-completion
Rebrand bash completion script
2015-01-22 12:52:27 -05:00
Harald Albers
f1e4fb7736 Rebrand bash completion script
Signed-off-by: Harald Albers <github@albersweb.de>
2015-01-22 16:07:11 +01:00
Aanand Prasad
df3221df61 Merge pull request #862 from aanand/ship-1.1.0rc1
Ship 1.1.0rc1
2015-01-20 22:50:39 +00:00
Aanand Prasad
43fdae8bc6 Ship 1.1.0-rc1
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 22:42:59 +00:00
Aanand Prasad
4869fed97f Merge pull request #835 from aanand/rebrand
Rebrand (pre-RC)
2015-01-20 22:24:21 +00:00
Aanand Prasad
16d6018419 Fix typos
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 21:28:49 +00:00
Aanand Prasad
8297f55f2c Remove all website-related stuff from docs
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 21:00:23 +00:00
Aanand Prasad
724be54f09 Manual fixes to docs
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 21:00:23 +00:00
Aanand Prasad
e3c4a662d9 Find-and-replace on docs
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 21:00:23 +00:00
Aanand Prasad
c981ce929a Update README.md, ROADMAP.md and CONTRIBUTING.md
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 21:00:23 +00:00
Aanand Prasad
620e29b63f Rename binary to docker-compose and config file to docker-compose.yml
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 21:00:23 +00:00
Aanand Prasad
2af7693e64 WIP: rename Fig to Compose
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 21:00:23 +00:00
Aanand Prasad
7be8b4c06d Merge pull request #851 from aanand/swarm-names
Handle Swarm-style prefixed container names
2015-01-20 20:51:47 +00:00
Aanand Prasad
cbd3ca07c4 Handle Swarm-style prefixed names in Container.from_ps()
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 20:38:06 +00:00
Aanand Prasad
edb6b24b8f Handle Swarm-style prefixed names in Service.containers()
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 20:38:06 +00:00
Aanand Prasad
608f29c7cb Unit tests for Service.containers() and get_container_name()
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 20:38:06 +00:00
Aanand Prasad
37ed743ee8 Merge pull request #859 from aanand/test-image-volumes
Test for preservation of volumes declared in images
2015-01-20 20:26:40 +00:00
Aanand Prasad
17a8a7be4b (Failing) test for preservation of volumes declared in images
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-01-20 20:16:18 +00:00
Aanand Prasad
f57db078ba Merge pull request #863 from dnephin/revert_volume_recreate_changes
Revert #711 from dnephin/fix_volumes_on_recreate
2015-01-20 20:15:17 +00:00
Daniel Nephin
2dd1cc80ca Revert "Merge pull request #711 from dnephin/fix_volumes_on_recreate"
This reverts commit 55095ef488, reversing
changes made to 72095f54b2.

Signed-off-by: Daniel Nephin <dnephin@yelp.com>
2015-01-20 12:03:03 -08:00
Aanand Prasad
55095ef488 Merge pull request #711 from dnephin/fix_volumes_on_recreate
Fix volumes on recreate
2015-01-15 18:53:48 +00:00
Daniel Nephin
7eb476e61d Resolves #447, fix volume logic for recreate container
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2015-01-14 16:20:59 -05:00
Aanand Prasad
72095f54b2 Merge pull request #639 from albers/bash-completion
Bash completion for fig command
2015-01-13 17:46:42 +00:00
Daniel Nephin
26f45efea2 Remove unused intermediate_container from return value.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2015-01-12 22:16:54 -08:00
Aanand Prasad
4827e60641 Merge pull request #568 from Aigeruth/fix/project_name
Convert project_name to lowercase
2015-01-12 16:23:09 +00:00
Aanand Prasad
74a0c47389 Merge pull request #830 from ggtools/cpu_shares
Add cpu_shares option in fig.yml
2015-01-12 12:09:08 +00:00
Christophe Labouisse
aa0c43df96 Add cpu_shares option in fig.yml
This options maps exactly the docker run option with the same name.

Signed-off-by: Christophe Labouisse <christophe@labouisse.org>
2015-01-11 20:10:48 +01:00
Aanand Prasad
4ef2e21cca Merge pull request #815 from dnephin/fix_the_build
Fix the failing test
2015-01-09 12:36:09 +00:00
Daniel Nephin
3ee8437eaa Fix the failing test.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2015-01-08 10:55:40 -05:00
Daniel Nephin
b903217a4a Merge pull request #544 from LuminosoInsight/external-links
Allow links to containers outside of the project
2015-01-08 10:39:00 -05:00
Harald Albers
2406a3936a Documentation for bash completion
Signed-off-by: Harald Albers <github@albersweb.de>
2015-01-08 15:18:39 +01:00
Harald Albers
69db596b5d Bash completion for fig command
Signed-off-by: Harald Albers <github@albersweb.de>
2015-01-08 15:18:39 +01:00
Richard Adams
9a90a27376 be explicit with a ruby version number in the Dockerfile
Signed-off-by: Richard Adams <richard@madwire.co.uk>
2015-01-07 19:45:00 +00:00
Aanand Prasad
a2f3c0b5da Merge pull request #804 from bfirsh/roadmap-update
Reorganised roadmap, add high-level goals
2015-01-07 19:19:45 +00:00
Richard Adams
3b638f0c43 tweaks to the rails tutorial to bring it inline with rail 4.2 release
Due to a change in Rack, rails server now listens on localhost instead of 0.0.0.0 by default.

Signed-off-by: Richard Adams <richard@madwire.co.uk>
2015-01-07 17:06:47 +00:00
Ben Firshman
b252300e94 Reorganised roadmap, adding high-level goals
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2015-01-06 15:39:02 +00:00
Daniel Nephin
be553e393a Merge pull request #787 from ggtools/patch-1
Added missing options
2015-01-05 13:05:48 -05:00
Christophe Labouisse
91c90a722a Added missing options
The stdin_open and tty options are supported by fig but were missing from the documentation.

Signed-off-by: Christophe Labouisse <christophe@labouisse.org>
2015-01-03 23:20:10 +01:00
Ben Firshman
d399d386ae Merge pull request #765 from aanand/roadmap
Docker Compose roadmap
2014-12-31 19:25:39 +00:00
Daniel Nephin
4cb47e498b Merge pull request #586 from dnephin/speed_up_fig_up
Speed up fig up
2014-12-31 14:00:07 -05:00
Daniel Nephin
3056ae4be3 Add a no-build option to fig up, to save time when services were already freshly built.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-12-31 13:47:24 -05:00
Daniel Nephin
5b777ee5f1 Cleanup service unit tests and restructure some service create logic.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-12-31 13:39:41 -05:00
Aanand Prasad
c885aaa5f8 ROADMAP.md
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-12-31 18:10:05 +00:00
Jason Bernardino Alonso
4257707244 Accept an external_links list in the service configuration dictionary to create links to containers outside of the project
Signed-off-by: Jason Bernardino Alonso <jalonso@luminoso.com>
Signed-off-by: Mauricio de Abreu Antunes <mauricio.abreua@gmail.com>
2014-12-30 15:31:33 -05:00
Aanand Prasad
70c3676084 Merge pull request #763 from bfirsh/pass-through-detach-to-create-container
Don't attach stdin and stdout when in detach mode
2014-12-30 16:05:53 +00:00
Ben Firshman
e89826fe43 Don't attach stdin and stdout when in detach mode
This is primarily to make it work with Swarm, which checks that
AttachStd{in,out,err} is false when creating containers.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-12-30 14:13:36 +00:00
Aanand Prasad
0e172228d1 Merge pull request #485 from squebe/run-service-ports
Added map service ports option for run command.
2014-12-19 14:17:57 -08:00
Daniel Nephin
cb1a5e0a24 Merge pull request #476 from salehe/dns-search
Add dns_search support in yml config
2014-12-19 10:00:45 -05:00
Mohammad Salehe
3c105c6db2 Fix typo in dns_search documentation
Signed-off-by: Mohammad Salehe <salehe+dev@gmail.com>
2014-12-15 16:37:32 +03:30
Mohammad Salehe
5182bd0968 Add dns_search support in yml config
Signed-off-by: Mohammad Salehe <salehe@gmail.com>
2014-12-15 16:37:31 +03:30
Daniel Nephin
200c44cff3 Merge pull request #728 from aanand/volume_test
Stronger integration test for volume binds
2014-12-13 12:51:22 -05:00
Aanand Prasad
05544ce241 Stronger integration tests for volume binds
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-12-12 17:53:57 -08:00
Stephen Quebe
cc834aa564 Added map service ports option for run command.
When using the fig run command, ports defined in fig.yml can be mapped
to the host computer using the -service-ports option.

Signed-off-by: Stephen Quebe <squebe@gmail.com>
2014-12-11 20:30:38 -05:00
Ben Firshman
a5505e7711 Merge pull request #721 from dnephin/fix_broken_tests
Include image name for env_file tests
2014-12-12 00:46:41 +00:00
Daniel Nephin
8f8e322de2 Include image name for env_file tests.
Signed-off-by: Daniel Nephin <dnephin@yelp.com>
2014-12-11 14:25:26 -08:00
Daniel Nephin
64762c9dea Merge pull request #716 from bfirsh/pull-latest-tag-by-default
Pull latest tag by default
2014-12-11 15:58:40 -05:00
Ben Firshman
8ebec9a67f Pull latest tag by default
This was changed in Docker recently:

https://github.com/docker/docker/pull/7759

This means we aren't pulling loads of tags when we only use the
latest.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-12-11 12:37:04 -08:00
Ben Firshman
45b2712032 Merge pull request #720 from dnephin/upstream_minor_fixes
Support a timeout on docker client
2014-12-11 19:28:33 +00:00
Daniel Nephin
3c0f297ba6 Some minor cleanup from yelp/fig
Signed-off-by: Daniel Nephin <dnephin@yelp.com>
2014-12-11 10:08:39 -08:00
Ben Firshman
c0123c7477 Merge pull request #717 from bfirsh/rm-containers-in-scripts
Remove containers in scripts
2014-12-10 23:26:51 +00:00
Ben Firshman
2a0782c660 Merge pull request #718 from bfirsh/docker-py-0.6.0
Upgrade to docker-py 0.6.0
2014-12-09 22:12:05 +00:00
Daniel Nephin
481a8cb7ab Merge pull request #713 from leishman/add-favicon-headers
Add favicon.ico and links in header
2014-12-09 14:29:33 -05:00
Ben Firshman
788741025e Upgrade to docker-py 0.6.0
Force using remote API version 1.14 so Fig is still compatible with
Docker 1.2.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-12-09 10:57:45 -08:00
Ben Firshman
c12d1d73f0 Remove containers in scripts
So we don't clutter up Docker with loads of stopped containers.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-12-09 10:49:37 -08:00
Ben Firshman
e794e79209 Merge pull request #665 from benlangfeld/support-env-file
Support env file
2014-12-09 18:42:31 +00:00
Ben Langfeld
98b6d7be78 Add support for 'env_file' key
Signed-off-by: Ben Langfeld <ben@langfeld.me>
2014-12-08 23:42:09 +00:00
Ben Firshman
a12cf826cd Merge pull request #585 from alunduil/add-tests-to-sdist
Add tests to sdist.
2014-12-08 21:34:47 +00:00
Ben Firshman
d18cfa1c98 Merge pull request #661 from bfirsh/add-entrypoint-to-dockerfile
Add fig as entrypoint to Dockerfile
2014-12-08 21:32:06 +00:00
Ben Firshman
9a04ae0ddf Merge pull request #623 from TFenby/capabilities
Add capability add/drop introduced in Docker 1.2
2014-12-08 21:24:02 +00:00
Alexander Leishman
ab77cef7ab Add favicon.ico and links in header
Signed-off-by: Alexander Leishman <leishman3@gmail.com>
2014-12-08 13:04:02 -08:00
Tyler Fenby
5c58180538 Add capability add/drop introduced in Docker 1.2
Signed-off-by: Tyler Fenby <tylerfenby@gmail.com>
2014-12-08 12:32:52 -05:00
Ben Firshman
429a3feabc Merge pull request #699 from dtenenba/master
interpolate service_name in error message
2014-12-08 08:49:54 -08:00
Ben Firshman
1033439e63 Merge pull request #701 from bersace/master
Respect --allow-insecure-ssl option for dependencies
2014-12-08 08:46:30 -08:00
Ben Firshman
e9d946b038 Merge pull request #594 from popox/restart-option
Add restart option to Fig.
2014-12-08 08:43:27 -08:00
Étienne BERSAC
4e8337c168 Respect --allow-insecure-ssl option for dependencies
Signed-off-by: Étienne Bersac <etienne.bersac@novapost.fr>
2014-12-04 11:45:36 +01:00
Dan Tenenbaum
e34a62956e interpolate service_name in error message when service has no configuration options.
Signed-off-by: Dan Tenenbaum <dtenenba@fhcrc.org>
2014-12-03 09:44:35 -08:00
Alex Brandt
0150b38b8f Add tests to sdist.
In order to validate installation it's very convenient to have the tests
as part of the source distribution.  This greatly assists native
packaging that might occur (i.e. Gentoo ebuilds).

Signed-off-by: Alex Brandt <alunduil@alunduil.com>
2014-11-28 09:37:33 -06:00
Ben Firshman
bb85e238e0 Add fig as entrypoint to Dockerfile
A step towards "docker run fig".

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-11-20 17:23:43 +00:00
Daniel Nephin
65ae22e79a Merge pull request #611 from raulcd/604_support_reloading_containers
Add signal in the kill CLI commando to send a specific signal to the service
2014-11-13 10:28:14 -05:00
Paul B
04da6b035e Add restart option to Fig. Related to #478
Signed-off-by: Paul Bonaud <paul@bonaud.fr>
2014-11-08 12:10:11 +01:00
Daniel Nephin
d3e94f2caf Merge pull request #624 from drewkett/multiple_port_mappings
Support multiple port bindings for same internal port
2014-11-07 11:28:27 -08:00
Andrew Burkett
4f6d02867b Move to build_port_bindings(). Added Tests
Signed-off-by: Andrew Burkett <burkett.andrew@gmail.com>
2014-11-06 18:11:09 -08:00
Andrew Burkett
f98323b79e Support multiple port mappings for same internal port
Signed-off-by: Andrew Burkett <burkett.andrew@gmail.com>
2014-11-06 16:29:39 -08:00
Aanand Prasad
3f4b16181d Merge pull request #618 from bfirsh/make-dnephin-a-maintainer
Make @dnephin a maintainer
2014-11-05 10:58:50 +00:00
Aanand Prasad
5dde2a2498 Merge pull request #612 from bfirsh/ship-1.0.1
Ship 1.0.1
2014-11-05 10:57:53 +00:00
Ben Firshman
06a1b32c12 Make @dnephin a maintainer
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-11-05 09:47:48 +00:00
Ben Firshman
46433c70b6 Ship 1.0.1
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-11-04 14:12:48 +00:00
Aanand Prasad
9260603149 Merge pull request #588 from bfirsh/use-upstream-dockerpty
Use upstream dockerpty 0.3.2
2014-11-04 12:59:01 +00:00
Raúl Cumplido
9abdd337b5 Add signal in the kill CLI commando to send a specific signal to the service
Signed-off-by: Raúl Cumplido <raulcumplido@gmail.com>
2014-11-04 12:22:39 +00:00
Ben Firshman
8773f51583 Don't select stdin when interactive=False
Patch from https://github.com/d11wtq/dockerpty/pull/24

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-11-04 10:25:27 +00:00
Ben Firshman
38a3ee8d63 Use upstream dockerpty 0.3.2
This reverts commit 60411e9f05.

Closes #556

Signed-off-by: Ben Firshman <ben@firshman.co.uk>

Conflicts:
	requirements.txt
	setup.py
2014-11-04 10:23:42 +00:00
Aanand Prasad
cb275b8633 Merge pull request #591 from npeters/fix-insecure-registry
fix insecure parameter
2014-11-03 14:17:43 +00:00
Aanand Prasad
604b370bb4 Merge pull request #606 from kojiromike/602-no-help-without-figfile
Fixes #602 Allowing `help $cmd` with no figfile
2014-11-03 11:27:58 +00:00
Michael A. Smith
782a46fd60 Fixes #602 Allowing help $cmd with no figfile
Signed-off-by: Michael A. Smith <michael@smith-li.com>
2014-11-02 06:22:16 -05:00
Aanand Prasad
1bd3d0dd77 Merge pull request #584 from kevinsimper/patch-1
Update wordpress url on wordpress example
2014-10-31 12:23:30 +00:00
Nicolas Peters
b64ea85916 fix insecure parameter
Signed-off-by: Nicolas Peters <peters.nico@gmail.com>
2014-10-28 00:05:17 +01:00
Kevin Simper
b47ab2b0f6 Update wordpress url on wordpress example
The old url gave a 302 and wordpress 4.0 has also been released.
2014-10-24 23:50:06 +02:00
Aanand Prasad
11280e4f30 Merge pull request #581 from bfirsh/add-dockerignore
Add .dockerignore
2014-10-24 11:59:01 +01:00
Aanand Prasad
a83876da09 Merge pull request #580 from bfirsh/improve-contributing-instructions
Improve contributing instructions
2014-10-24 11:43:14 +01:00
Ben Firshman
e66c0452d5 Add .dockerignore
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-23 19:00:44 +01:00
Ben Firshman
899670fc6c Move building binaries instructions
It's less important than signing your work.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-23 18:31:28 +01:00
Ben Firshman
ea45715a50 Tidied up development environment instructions
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-23 18:30:48 +01:00
Ben Firshman
28f9c8d047 Add TL;DR section to CONTRIBUTING.md
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-23 18:28:17 +01:00
Aanand Prasad
292fe6640e Merge pull request #578 from bfirsh/update-url-in-twitter-button
Update Twitter button URL
2014-10-23 16:17:38 +01:00
Ben Firshman
b759b9854a Update Twitter button URL
I originally left it as orchardup.github.io so the number wouldn't roll
back to 0, but people have been tweeting about fig.sh now.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-23 16:05:11 +01:00
Aanand Prasad
40943d5c81 Merge pull request #533 from bfirsh/mention-configuration-files-on-home-page
Mention names of config files on home page
2014-10-23 11:23:33 +01:00
Aanand Prasad
93a0195dc8 Merge branch 'andreagrandi-patch-1'
Closes #570.
2014-10-23 11:14:53 +01:00
Andrea Grandi
7f0745d146 Add command line options to the documentation
Signed-off-by: Andrea Grandi <a.grandi@gmail.com>
2014-10-23 11:13:40 +01:00
Ben Firshman
9813a8d5be Merge pull request #562 from tjrivera/insecure-registry
Allow dependent image pull from insecure registry
2014-10-22 18:45:03 +01:00
Tyler Rivera
491181ec31 Allow dependent image pull from insecure registry
PR #490 Provides the ability to pull from an insecure registry by passing --allow-insecure-ssl. This commit extends the work done in #490 and adds the ability to pass --allow-insecure-ssl to the up and run commands which will attempt to pull dependent images if they do not exist.

Signed-off-by: Tyler Rivera <riverat2@email.chop.edu>
2014-10-22 10:57:43 -04:00
Aanand Prasad
e1ad5b1b99 Merge pull request #575 from bfirsh/fix-google-analytics-code
Fix Google Analytics code
2014-10-22 12:48:45 +01:00
Aanand Prasad
67d4b7c587 Merge pull request #574 from bfirsh/update-tagline
Update tagline to match website
2014-10-22 12:46:16 +01:00
Ben Firshman
98f663bab2 Fix Google Analytics code
It was not reporting because our URL changed.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-22 12:46:05 +01:00
Ben Firshman
75b3dcf5fd Update tagline to match website
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-22 12:41:01 +01:00
Ben Firshman
deeb6c2236 Merge pull request #567 from aanand/fix-wordpress-guide
Fix language in Wordpress guide
2014-10-22 11:31:41 +01:00
Gabor Nagy
c838f7da18 Convert project_name to lowercase
Signed-off-by: Gabor Nagy <mail@aigeruth.hu>
2014-10-21 13:40:19 +02:00
Aanand Prasad
e1e2b75691 Fix language in Wordpress guide
Was still talking about 'environment variables'.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-21 11:44:42 +01:00
Aanand Prasad
392c118bbc Merge pull request #554 from dnephin/more_defensive_stream
Fix a couple bugs with 1.0 release and Docker 1.3
2014-10-20 15:10:50 +01:00
Ben Firshman
dbd1e56dd3 Merge pull request #548 from aanand/fix-docker-py-requirement
Fix docker-py requirement in setup.py
2014-10-20 10:33:44 +01:00
Daniel Nephin
7544580b4b Resolves #553, Resolves #546 - bug fixes with unit tests
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-10-18 13:54:10 -04:00
Aanand Prasad
2efb4f5be0 Merge pull request #550 from bfirsh/fix-date-on-change-notes
Fix date on 1.0.0 change notes
2014-10-17 15:47:50 +01:00
Ben Firshman
5e8dc6c972 Merge pull request #549 from aanand/credit-1.0-contributors
Credit 1.0 contributors
2014-10-17 15:43:34 +01:00
Ben Firshman
23c8ec8930 Fix date on 1.0.0 change notes
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-17 15:43:14 +01:00
Aanand Prasad
bd8affb7aa Credit 1.0 contributors
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-17 14:54:46 +01:00
Aanand Prasad
7a943739cb Fix docker-py requirement in setup.py
I updated requirements.txt but forgot about setup.py, so the version on
pypi is broken for people who already have docker-py 0.5.0 installed.

Closes #547.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-17 11:17:58 +01:00
Aanand Prasad
6580c5609c Merge pull request #526 from bfirsh/ship-1.0.0
WIP: Ship 1.0.0
2014-10-16 18:54:56 +01:00
Ben Firshman
a07c83659d Merge pull request #537 from aanand/tls
TLS support
2014-10-16 18:53:59 +01:00
Aanand Prasad
0f58b9f6b4 Update docker-py to 0.5.3
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-16 18:46:55 +01:00
Aanand Prasad
fed391a23e Update documentation for TLS and boot2docker
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-16 18:46:48 +01:00
Ben Firshman
4d1b2f1547 Ship 1.0.0
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-16 16:27:13 +01:00
Aanand Prasad
60411e9f05 Vendor dockerpty at c8b493553477c9a57d163c71c97b2102f44a6ce7
Include TLS fixes and relative imports:

c8b4935534

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-16 16:24:39 +01:00
Aanand Prasad
b318585f3c TLS support, with same env vars as docker client
Thanks to @jkingyens for the bulk of the work.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-16 10:41:26 +01:00
Ben Firshman
1820306d0a Merge pull request #529 from chmouel/override-environement
Allow overriding environements on command line
2014-10-14 11:27:20 +01:00
Aanand Prasad
bf1c1b4c17 Merge pull request #534 from bfirsh/use-bin-echo-for-intermediate-container
Use /bin/echo for intermediate container
2014-10-10 13:12:33 +01:00
Ben Firshman
352062c2dc Use /bin/echo for intermediate container
In cases where the service is using a minimal container,
/bin/echo can be created but echo cannot.

See #517

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-10 12:24:00 +01:00
Ben Firshman
d3f88cace5 Mention names of config files on home page
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-10 11:06:49 +01:00
Chmouel Boudjnah
92249364b6 Allow overriding environements on command line
Add a new command line option -e to override environement variables when
running a service.

Signed-off-by: Chmouel Boudjnah <chmouel@chmouel.com>
2014-10-09 16:33:41 +02:00
Ben Firshman
872a1b5a5c Merge pull request #523 from aanand/remove-references-to-docker-osx
Remove references to docker-osx
2014-10-06 16:05:06 +01:00
Aanand Prasad
b969988ccb Remove references to docker-osx
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-06 14:21:49 +01:00
Aanand Prasad
a5aac7d59e Merge pull request #508 from bfirsh/recommend-boot2docker-in-install-instructions
Recommend boot2docker in installation instructions
2014-10-06 13:44:23 +01:00
Ben Firshman
c16f4d4041 Recommend boot2docker in installation instructions
Fixes #26

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-06 13:14:12 +01:00
Aanand Prasad
d91c458d52 Merge pull request #509 from bfirsh/smarter-binary-urls
Use uname to generate binary download URL
2014-10-06 12:35:36 +01:00
Ben Firshman
475a7e19a9 Merge pull request #521 from aanand/clean-up-env-docs
Clean up environment variable docs
2014-10-06 12:34:35 +01:00
Aanand Prasad
f43bfaadaa Clean up environment variable docs
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-10-06 11:58:16 +01:00
Aanand Prasad
2680756dd6 Merge pull request #518 from dnephin/env_docs
Add environment variables to cli.md docs
2014-10-06 11:36:21 +01:00
Daniel Nephin
837f368361 Add environment variables to cli.md docs.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-10-03 13:47:54 -04:00
Aanand Prasad
9df5481066 Merge pull request #512 from bfirsh/fix-entrypoint
Fix fig run entrypoint option
2014-10-01 15:39:00 -07:00
Aanand Prasad
59c7528b4e Merge pull request #513 from bfirsh/update-wercker-badge
Update wercker badge
2014-10-01 14:57:47 -07:00
Ben Firshman
d2385e3c2c Fix fig run entrypoint option
Slipped through because Wercker didn't report build status.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-01 14:19:23 -07:00
Ben Firshman
6b600faf0b Update wercker badge
I moved the project.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-01 12:50:30 -07:00
Aanand Prasad
431fdaa0f1 Merge pull request #490 from LuminosoInsight/insecure-pull
Allow pulls from an insecure registry
2014-10-01 10:52:54 -07:00
Aanand Prasad
ed12f2539c Merge pull request #511 from bfirsh/support_entrypoint
Add support for entrypoint to "fig run"
2014-10-01 10:51:50 -07:00
Ben Firshman
0dc19cb885 Order "fig run" options alphabetically
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-10-01 09:28:28 -07:00
satoru
62b9c64311 Add support for the --entrypoint option of docker run
Signed-off-by: Satoru Logic <satorulogic@gmail.com>
2014-10-01 09:28:28 -07:00
Moss Collum
3408e0d463 Add unit test for Service.pull
Signed-off-by: Moss Collum <mcollum@luminoso.com>
2014-10-01 11:09:19 -04:00
Ben Firshman
f4b599551a Use uname to generate binary download URL
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-09-30 18:59:18 -07:00
Aanand Prasad
267be12bb2 Merge pull request #456 from dnephin/volumes_from_service
Fix volumes_from a service with no containers
2014-09-30 18:33:39 -07:00
Aanand Prasad
ec5c864cc7 Merge pull request #460 from dnephin/fix_tests_for_docker_1.1
Fix test failures on docker 1.1.2
2014-09-30 17:52:38 -07:00
Aanand Prasad
cabe47a379 Merge pull request #505 from bfirsh/use-debian-wheezy-base-image
Compile against older version of GLIBC
2014-09-30 17:07:15 -07:00
Ben Firshman
b4fbab4b56 Run pyinstaller build as normal user
... and test build on CI so we don't break it again!

Fixes #503

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-09-30 16:25:40 -07:00
Alexander Holbreich
6797a322b5 Changing to stable debian (wheezy).
Therefore it compiles agains more common version of GLIBC.

Now works out of the box on Debian wheezy, centos:centos6 and other stable
Of course it works on new distributions. Tested with:
    debian:jessie and
    ubuntu:14.04

Signed-off-by: Alexander Holbreich <alexander@holbreich.org>

Conflicts:
	Dockerfile
2014-09-30 16:25:13 -07:00
Aanand Prasad
e04c5cb52c Merge pull request #506 from bfirsh/make-test-script-use-docker
Make script/test use docker
2014-09-30 15:16:07 -07:00
Ben Firshman
a3f70a9f64 Make script/test use Docker
Really easy to run entire test suite with Docker now. Also switch
Wercker to use the same script.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-09-30 13:51:42 -07:00
Ben Firshman
f407504679 Remove old Travis files
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-09-30 13:47:47 -07:00
Aanand Prasad
253b245a1c Merge pull request #492 from dnephin/project_name_from_env
Support setting project name from the environment
2014-09-29 15:32:29 -07:00
Daniel Nephin
fac49b62b6 Support setting project name from the environment.
Signed-off-by: Daniel Nephin <dnephin@yelp.com>
2014-09-29 18:01:08 -04:00
Ben Firshman
92ae5af019 Merge pull request #501 from aanand/fix-build-error
Fix race condition in cli_test.py
2014-09-28 04:54:08 +01:00
Jason Bernardino Alonso
c270e9d622 Require docker-py 0.5 or later for insecure_registry kwarg
Signed-off-by: Jason Bernardino Alonso <jalonso@luminoso.com>
2014-09-26 16:36:42 -04:00
Jason Bernardino Alonso
1c5194e2ec Allow pulls from an insecure registry
Signed-off-by: Jason Bernardino Alonso <jalonso@luminoso.com>
2014-09-26 16:36:36 -04:00
Aanand Prasad
537d435a28 Fix race condition in cli_test.py
We weren't waiting for the build to finish, causing the rest of the test
to occasionally fail when looking for the image.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-09-26 11:42:16 -07:00
Aanand Prasad
5d76d183b4 Merge pull request #483 from dnephin/sort_containers_by_name_in_ps
Sort containers in ps output by name
2014-09-26 10:18:08 -07:00
Aanand Prasad
d4b7ed94e1 Merge pull request #499 from bfirsh/wercker-badge
Add wercker badge to readme
2014-09-25 11:37:03 -07:00
Aanand Prasad
d978787fcc Merge pull request #439 from dnephin/faster_integration_tests
Faster integration testing
2014-09-24 17:34:52 -07:00
Ben Firshman
3535270ef0 Add wercker badge to readme
And removed PyPi badge because who cares.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-09-24 14:19:27 -07:00
Ben Firshman
b9eb55a225 Merge pull request #498 from bfirsh/mieciu-patch-1
Fix the broken URL
2014-09-24 21:53:57 +01:00
mieciu
35b217a0a4 Fix the broken URL
It was not redirecting to the proper page

Signed-off-by: Przemek Hejman <przemyslaw.hejman@gmail.com>
2014-09-24 13:52:08 -07:00
Aanand Prasad
c37dc558fb Merge pull request #497 from bfirsh/wercker
Add wercker.yml
2014-09-24 10:51:56 -07:00
Ben Firshman
7a8f5e10fd Add wercker.yml
Changed Dockerfile to run as root so it has access to
/var/run/docker.sock.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-09-23 10:40:51 -07:00
Daniel Nephin
192fce9153 Resolves #43 - sort containers in ps output by name, so services are grouped together.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-09-14 16:06:23 -04:00
Ben Firshman
fc4c35e977 Merge pull request #411 from Banno/fig-pull
adding "fig pull [SERVICE]" to pull service images
2014-09-10 23:57:20 +01:00
Luke Amdor
648c89768b adding 'fig pull' to cli docs
Signed-off-by: Luke Amdor <luke.amdor@gmail.com>
2014-09-10 16:20:14 -05:00
Daniel Nephin
e0b0801e87 Do less work during integration testing.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-09-09 22:00:21 -04:00
Daniel Nephin
71e7103662 Fix some tests failing with docker 1.1.2 and add a comment to recreate_container() explaining what it does.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-09-09 20:59:28 -04:00
Daniel Nephin
dbd723659b Add container.get() which removes the duplication of container.inspect() in every property, and provides a nicer interface for querying container data.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-09-09 20:59:28 -04:00
Aanand Prasad
6b221d5687 Merge pull request #477 from bfirsh/docker-py-0.5.0
Upgrade to docker-py 0.5.0
2014-09-08 14:20:12 -07:00
Ben Firshman
ce8ef23c09 Merge pull request #393 from marksteve/restart
Implement restart command (Closes #98)
2014-09-08 17:47:08 +01:00
Ben Firshman
b0159e5100 Upgrade to docker-py 0.5.0
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-09-08 09:23:01 -07:00
Ben Firshman
ee6bb9a252 Merge pull request #475 from bfirsh/fix-missing-six-package
Fix missing six package
2014-09-06 02:20:55 +01:00
Ben Firshman
866050937a Fix missing six package
I had some .pyc files kicking around, urgh.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-09-05 17:19:53 -07:00
Ben Firshman
41ee65b664 Merge branch 'dnephin-replace_packages_with_deps'
Closes #375
2014-09-05 11:55:17 -07:00
Daniel Nephin
7fd37c89b9 Remove fig.packages replace with real deps.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-09-05 11:44:49 -07:00
Aanand Prasad
8d3c9dccc5 Merge pull request #402 from dnephin/fig_ports.rebase
Fig port command
2014-09-05 11:09:40 -07:00
Daniel Nephin
f5d43b6452 Resolves #455 - volumes_from a service with no containers.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-09-04 22:21:49 -04:00
Daniel Nephin
c48ee5caef Add a new fig command for retrieving the locally bound port of a service.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-09-04 22:09:12 -04:00
Ben Firshman
2827786886 Merge pull request #364 from docker/non-numeric-link-alias
Non-numeric link alias
2014-09-04 20:39:18 +01:00
Aanand Prasad
7ad91f3f00 Merge pull request #442 from dnephin/fix_create_container_volumes
Additional validation for container volumes and ports.
2014-09-04 12:12:09 -07:00
Daniel Nephin
24044fa704 Some additional validation for container ports, and a couple extra test cases.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-08-31 02:00:58 -04:00
Daniel Nephin
07fa169fd2 Resolves #260, #301 and #449
Adds ~ support and ro mode support for volumes, along with some additional validation.

Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-08-31 02:00:52 -04:00
Daniel Nephin
8157f0887d Fix the return value of get_tty_width() it should return an int.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-08-25 22:20:07 -04:00
Aanand Prasad
6dab8c1b89 Merge pull request #420 from bfirsh/inline-installation-commands
Collapse install instructions into single line
2014-08-21 17:28:08 -07:00
Ben Firshman
63bd05d40e Collapse install instructions into single line
For maximum copy and paste happiness

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-18 13:36:37 -07:00
Luke Amdor
e51851c884 adding "fig pull [SERVICE]" to pull service images
Fixes #158

Signed-off-by: Luke Amdor <luke.amdor@gmail.com>
2014-08-15 09:24:15 -05:00
Mark Steve Samson
e224c4caa4 Add integration test for restart command
Signed-off-by: Mark Steve Samson <hello@marksteve.com>
2014-08-13 10:01:27 +08:00
Aanand Prasad
dc857a7ad5 Merge pull request #404 from bfirsh/validate-dco
Validate DCO on Travis
2014-08-12 11:20:38 -07:00
Ben Firshman
aca0e42178 Validate DCO on Travis
Copied straight from Docker, replacing github.com/docker/docker
with github.com/docker/fig in .validate.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-12 10:58:16 -07:00
Ben Firshman
15037ce0e5 Merge pull request #397 from alunduil/master
Exclude tests package from installation.
2014-08-12 18:16:50 +01:00
Mark Steve Samson
9d55e01e2a Implement restart command (Closes #98)
Signed-off-by: Mark Steve Samson <hello@marksteve.com>
2014-08-12 10:07:20 +08:00
Aanand Prasad
8eee2bf913 Merge pull request #396 from dnephin/remove_extra_calls
Remove extra calls, test bug fixes
2014-08-11 11:54:57 -07:00
Alex Brandt
3965db9dff Exclude tests package from installation.
Installing the top-level tests package is asking for conflicts with
other python packages and isn't required to run fig.  This simply lets
find_packages know to ignore tests and any sub-packages.
2014-08-11 09:00:16 -05:00
Daniel Nephin
294453433d Remove extra calls to docker server.
Fix broken integration tests

Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-08-10 11:24:20 -04:00
Chris Corbyn
22f897ed09 Merge pull request #370 from dnephin/debug_option
Add a --debug flag for debugging docker calls
2014-08-10 21:36:17 +10:00
Daniel Nephin
df7c2cc43f Resolves #369, add verbose output on --verbose flag
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-08-09 21:05:54 -04:00
Aanand Prasad
f2bf7f9e0d Merge pull request #384 from timfreund/master
Enable monochrome output in the 'up' and 'logs' commands
2014-08-08 14:55:31 -07:00
Tim Freund
69c241ba12 Enable monochrome output in the 'up' and 'logs' commands
Some systems, like Jenkins or other build servers, cannot correctly
render ANSI color codes.  The '--no-color' option enables monochrome
output in the 'up' and 'logs' commands to improve readability in those
systems.

Signed-off-by: Tim Freund <tim@freunds.net>
2014-08-08 16:15:27 -04:00
Aanand Prasad
59e31ff544 Update docs to remove numeric suffix
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-08-08 13:08:27 -07:00
Aanand Prasad
62a4d214e8 Default link alias which is just the service name
Closes #37.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-08-08 13:05:42 -07:00
Aanand Prasad
73bd4aca74 Use hostnames everywhere in docs, add YAML note and deprecate env.md
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-08-08 11:58:41 -07:00
Aanand Prasad
342ed948ec Merge pull request #379 from docker/use-library-python-image-for-dockerfile
Use library python image for dockerfile
2014-08-07 17:06:38 -07:00
Ben Firshman
3fc7ad3291 Don't use deprecated orchardup/python image
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 16:43:59 -07:00
Aanand Prasad
a39460d7b2 Merge pull request #392 from bfirsh/more-official-images
More official images
2014-08-07 16:42:36 -07:00
Chris Corbyn
6ab084a338 Merge pull request #390 from bfirsh/remove-integration-tests-from-ci
Remove integration tests from ci
2014-08-08 08:37:12 +10:00
Ben Firshman
406425a6b9 Use official images in rails tutorial
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 14:54:59 -07:00
Chris Corbyn
b690b0d20e Merge pull request #387 from dnephin/make_ps_work_on_jekins
tty width on jenkins
2014-08-08 07:54:00 +10:00
Ben Firshman
796df302dd Use official images on getting started guide
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 14:50:55 -07:00
Aanand Prasad
1346805bef Merge pull request #391 from bfirsh/repository-move
github.com/docker/fig
2014-08-07 14:49:26 -07:00
Ben Firshman
16744bc78b Switch to official images in readme
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 14:48:29 -07:00
Ben Firshman
fc3c12ad90 Update repository URL
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 14:42:49 -07:00
Ben Firshman
bbcbe9df9f Upload PyPi package manually
This never worked properly anyway.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 14:36:49 -07:00
Ben Firshman
1a240f50ae Remove integration tests from Travis
Orchard is going away and Travis will be able to run these soon
anyway.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 14:35:22 -07:00
Ben Firshman
47970761c5 Remove Orchard message from readme
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 14:19:27 -07:00
Aanand Prasad
df7bc8cbb8 Merge pull request #389 from orchardup/update-irc-channel
Update IRC channel to be #docker-fig
2014-08-07 13:43:12 -07:00
Ben Firshman
ba9d744293 Update IRC channel to be #docker-fig
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 13:39:27 -07:00
Aanand Prasad
d5854bb625 Merge pull request #388 from orchardup/dockerize
Point at Docker website and Docker IRC channel
2014-08-07 10:16:16 -07:00
Ben Firshman
e5f7690137 Point at Docker website and Docker IRC channel
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-07 10:15:01 -07:00
Daniel Nephin
b0f398caaa Resolves #386, tty width on jenkins
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-08-07 12:30:01 -04:00
Aanand Prasad
0a15e7fe9c Merge pull request #378 from orchardup/use-ruby-image-for-rails-tutorial
Use ruby image for rails docs
2014-08-06 16:51:04 -07:00
Aanand Prasad
255b9419dd Merge pull request #382 from orchardup/use-official-repositories-in-django-example
Use official images in Django example
2014-08-06 16:50:45 -07:00
Aanand Prasad
c63947b5e2 Merge pull request #383 from orchardup/use-official-repos-on-homepage
Use official repos on home page
2014-08-06 16:40:48 -07:00
Ben Firshman
b5d1979d58 Use official repos on home page
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-06 16:33:31 -07:00
Ben Firshman
94aa097bc3 Use official images in Django example
Much neater.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-06 16:30:27 -07:00
Aanand Prasad
72ce7ce374 Merge pull request #380 from orchardup/use-hostname-for-redis-on-home-page-example
Use hostname on home page example
2014-08-06 16:11:44 -07:00
Ben Firshman
6d64f20ad6 Use hostname on home page example
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-06 15:53:45 -07:00
Ben Firshman
506f54e9c3 Use ruby image for rails docs
Fixes #376

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-08-06 13:42:56 -07:00
Chris Corbyn
90f5eda930 Merge pull request #367 from dnephin/fix_366_python_dependencies
Don't pin versions in setup.py install_requires
2014-08-05 08:17:23 +10:00
Daniel Nephin
c0450f7df0 Resolves #366, non-pinned versions in setup.py:install_requires
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-07-29 17:13:22 -07:00
d11wtq
847ec5b559 Update dockerpty -> 0.2.3
Signed-off-by: d11wtq <chris@w3style.co.uk>
2014-07-29 01:17:28 +00:00
Aanand Prasad
09ffa101ed Merge pull request #362 from orchardup/add-flake8-to-test-script
Add flake8 to test script
2014-07-28 16:34:07 -07:00
Ben Firshman
01e2b56405 Add flake8 to test script
For those who don't use tox.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-28 16:27:57 -07:00
Ben Firshman
2f6c763703 Merge pull request #353 from dnephin/add_flake8
Add flake8
2014-07-28 22:35:51 +01:00
Ben Firshman
4caf90c581 Merge pull request #361 from orchardup/update-release-process
Add "update the website" step to release process
2014-07-28 21:53:35 +01:00
Aanand Prasad
8fdeb46430 Add "update the website" step to release process
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-28 13:27:22 -07:00
Ben Firshman
07c47426ba Merge pull request #358 from orchardup/ship-0.5.2
Ship 0.5.2
2014-07-28 21:18:51 +01:00
Aanand Prasad
a6324d6226 Ship 0.5.2
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-28 13:17:08 -07:00
Daniel Nephin
939406ca9d Run flake8 in travis build
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-07-27 08:51:36 -07:00
Daniel Nephin
50a24bc3bf Add flake8 and fix errors.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-07-27 08:51:17 -07:00
Chris Corbyn
0dc55fda45 Merge pull request #322 from dnephin/fix_cli_for_py26
Fix cli for python 2.6
2014-07-27 18:35:40 +10:00
Chris Corbyn
dcd8e7863f Update dockerpty. Fixes #352. 2014-07-27 08:17:38 +00:00
Aanand Prasad
ed283fd3df Merge pull request #345 from orchardup/docker-inc
Docker, Inc.
2014-07-25 14:31:58 -07:00
Aanand Prasad
393433b702 Merge pull request #347 from orchardup/fix-typo-yml-docs
Fix typo yml docs
2014-07-24 14:31:19 -07:00
Ben Firshman
7516b67a14 Add link to docker-osx in yml docs
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-24 14:18:21 -07:00
Ben Firshman
5eac04d8d4 Fix typo in yml docs
From c09734822e

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-24 14:16:49 -07:00
Ben Firshman
fec41d3567 Merge pull request #288 from saulshanabrook/patch-1
Add warning for mapping local volumes
2014-07-24 22:15:49 +01:00
Saul Shanabrook
c09734822e Add warning for mapping local volumes
Mapping local volumes is not currently supported in boot2docker

https://github.com/boot2docker/boot2docker/issues/413
https://github.com/dotcloud/docker/issues/4023

Signed-off-by: Saul Shanabrook <s.shanabrook@gmail.com>
2014-07-24 16:37:14 -04:00
Aanand Prasad
94887a28c7 Make sure we support explicit protocols in port bindings
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-24 11:48:29 -07:00
Aanand Prasad
262efce43e Fix test regression introduced in ed80576236
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-24 11:47:39 -07:00
Ben Firshman
99064d17dd Docker, Inc.
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-24 10:24:17 -07:00
Aanand Prasad
ed80576236 Merge pull request #276 from marksteve/nocache
Add `--no-cache` option to `fig build` (Closes #152)
2014-07-23 10:13:47 -07:00
Ben Firshman
5131eaeba0 Merge pull request #335 from orchardup/document-missing-yml-options
Document missing .yml options
2014-07-23 03:45:52 +01:00
Aanand Prasad
b559880a80 Document missing .yml options
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-22 15:37:05 -07:00
Mark Steve Samson
7f06d46827 Add test for building with --no-cache
Signed-off-by: Mark Steve Samson <hello@marksteve.com>
2014-07-22 11:40:44 +08:00
Aanand Prasad
e1a0937a61 Merge pull request #330 from orchardup/www.fig.sh
www.fig.sh
2014-07-21 16:46:14 -07:00
Ben Firshman
59c976510c Update URL in readme and setup.py
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-21 16:22:03 -07:00
Ben Firshman
f189e299fd Add canonical link pointing at fig.sh
This will keep Google happy.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-21 16:20:43 -07:00
Ben Firshman
d08720247a Remove absolute link from docs
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-21 16:19:55 -07:00
Aanand Prasad
a4df76dd3f Merge pull request #329 from orchardup/update-to-docker-1.1.1
Update docs to Docker version 1.1.1
2014-07-21 11:46:33 -07:00
Ben Firshman
b2e3a91098 Update docs to Docker version 1.1.1
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-21 11:30:15 -07:00
Mark Steve Samson
47bbc35b74 Add --no-cache option to fig build (Closes #152)
Signed-off-by: Mark Steve Samson <hello@marksteve.com>
2014-07-20 11:28:28 +08:00
Ben Firshman
a12f3b40d5 Merge pull request #327 from orchardup/add-cname-to-github-sites
Add fig.sh CNAME to GitHub sites
2014-07-20 01:36:27 +01:00
Ben Firshman
3386927f9f Add fig.sh CNAME to GitHub sites
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-19 17:35:38 -07:00
Aanand Prasad
12d75a74e6 Merge pull request #325 from orchardup/fix-dns
Fix and document `dns:` option
2014-07-17 18:34:12 -07:00
Aanand Prasad
1a9c5e197d Merge pull request #324 from orchardup/add-maintainers-file
Add maintainers file
2014-07-17 18:32:33 -07:00
Ben Firshman
8fa85ecc05 Add maintainers file
Docker style: https://github.com/dotcloud/docker/blob/master/hack/MAINTAINERS.md

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-17 18:20:53 -07:00
Aanand Prasad
140ced6a3b Fix and document dns: option
Closes #240.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-17 18:11:50 -07:00
Daniel Nephin
779f4bda01 Fix cli for python 2.6
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
2014-07-15 13:07:46 -07:00
Ben Firshman
0021a06468 Merge pull request #321 from orchardup/update-install-docs-for-0.5.1
Update install docs for 0.5.1
2014-07-15 00:05:13 +01:00
Aanand Prasad
6ead40e14c Update install docs for 0.5.1
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-14 16:01:41 -07:00
Aanand Prasad
5e2308d14a CHANGES.md credits for 0.5.1
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-14 15:58:26 -07:00
Ben Firshman
9ab8f358ca Merge pull request #315 from orchardup/ship-0.5.1
Ship 0.5.1
2014-07-14 23:22:48 +01:00
Aanand Prasad
d9c9b5e1f0 Ship 0.5.1
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-14 13:36:32 -07:00
Aanand Prasad
730de9187a Merge pull request #320 from orchardup/add-egg-info-to-clean
Add fig.egg-info to clean script
2014-07-14 13:35:22 -07:00
Ben Firshman
bad0f45816 Merge pull request #319 from orchardup/fix-project-name-regression
Fix regression of default behaviour in Command.project_name
2014-07-14 20:31:20 +01:00
Ben Firshman
190ea2bbd6 Add fig.egg-info to clean script
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-14 11:34:17 -07:00
Aanand Prasad
91fe414522 Fix regression of default behaviour in Command.project_name
Needed an `os.abspath` in there. Added more tests, too.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-14 11:32:10 -07:00
Ben Firshman
7fb43cc85f Merge pull request #283 from d11wtq/feature/optional-command
Make fig run COMMAND parameter optional.
2014-07-14 18:33:18 +01:00
Ben Firshman
d6657ed16c Merge pull request #317 from ryanbrainard/project-name-dirname
Set default project name to dir name of fig.yml
2014-07-14 18:26:43 +01:00
Ben Firshman
48e7c86d66 Merge pull request #316 from orchardup/restructure-yaml-docs
Improve fig.yml docs
2014-07-14 18:24:50 +01:00
Ryan Brainard
e9c2f2c5fb Default project_name to dirname of fig.yml
Signed-off-by: Ryan Brainard <brainard@heroku.com>
2014-07-12 23:38:20 -07:00
Aanand Prasad
197fd77b99 Improve some of the .yml options docs
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-12 09:55:13 -07:00
Aanand Prasad
36bef254ff Restructure .yml docs
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-12 09:47:43 -07:00
Aanand Prasad
8e265905d3 Add host-in-port-mapping feature to docs and changelog
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-12 09:38:18 -07:00
Chris Corbyn
ef2fb77c1d Make fig run COMMAND parameter optional.
This behaves more like the native docker client, where the absence of a
command means docker runs the CMD in the Dockerfile. If a command is
defined in fig.yml this is used instead.

Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-07-12 02:30:34 +00:00
Ben Firshman
dd5c2e8767 Merge pull request #313 from orchardup/fix-volumes-from-container
Fix volumes_from container
2014-07-12 01:10:45 +01:00
Aanand Prasad
c255999fce Merge pull request #312 from orchardup/fix-default-values-for-service-args
Remove empty lists from default Service args
2014-07-11 17:06:55 -07:00
Aanand Prasad
89341013a0 Fix volumes_from container
Closes #311.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-11 16:39:23 -07:00
Ben Firshman
f983110492 Remove empty lists from default Service args
This will cause terrifying bugs.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-11 16:07:31 -07:00
Aanand Prasad
9fd296f416 Merge pull request #310 from orchardup/fix-race-condition-in-recreate-containers
Fix race condition in recreate containers
2014-07-11 15:14:17 -07:00
Ben Firshman
bb89f85984 Remove stop timeout in recreate containers
This doesn't seem like a good idea – 1 second isn't enough time
to stop gracefully.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-11 14:53:56 -07:00
Ben Firshman
b573b87a92 Fix race condition in recreate containers
Container might have stopped between checking `is_running` and
calling `stop()`, which then threw an exception.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-11 14:53:46 -07:00
Aanand Prasad
036adb2de9 Merge pull request #309 from orchardup/update-docs-to-be-0.5.0
Update docs to be 0.5.0
2014-07-11 14:29:38 -07:00
Ben Firshman
36f4e30dba Add 0.5.0 to docs
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-11 14:16:39 -07:00
Ben Firshman
9f0cfbdfd2 Document release process
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-11 14:16:29 -07:00
Aanand Prasad
e117a7822d Merge pull request #307 from orchardup/ship-0.5.0
Ship 0.5.0
2014-07-11 14:07:40 -07:00
Ben Firshman
5489465905 Ship 0.5.0
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-11 13:21:01 -07:00
Ben Firshman
4afcdbdb3c Add docs for volumes_from
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-11 12:52:22 -07:00
Ben Firshman
94d82d4acb Merge pull request #308 from orchardup/fix-fig-up-attach
Regardless of dependencies, `fig up` only attaches to what you specify
2014-07-11 20:51:54 +01:00
Aanand Prasad
d528f9f642 Regardless of dependencies, fig up only attaches to what you specify
Without this, if you go:

    $ fig up -d db
    $ fig up web

you'll get output for both db and web, and Ctrl-C will kill them both.

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-11 11:58:59 -07:00
Ben Firshman
99d7a474af Merge pull request #297 from ryanbrainard/resolve-env
Resolve environment without values to values on host
2014-07-11 18:55:40 +01:00
Ryan Brainard
d1052ff666 Add documention for key-only environment
Signed-off-by: Ryan Brainard <brainard@heroku.com>
2014-07-11 10:18:17 -07:00
Ryan Brainard
44a91e6ba8 Resolve environment without values to values on host
For parity with the Docker CLI, allow environment variables without
values to be automatically resolved to their values on the host.

Signed-off-by: Ryan Brainard <brainard@heroku.com>

Conflicts:
	tests/integration/service_test.py
2014-07-11 10:18:05 -07:00
Ben Firshman
3996947024 Merge pull request #304 from Elemecca/t-domainname
add support for domainname, qualified hostname
2014-07-11 17:40:19 +01:00
Ben Firshman
b7afaba56a Merge pull request #305 from ryanbrainard/fix-dir-typo
Fix typo in script dir name
2014-07-11 17:37:13 +01:00
Ryan Brainard
2ce3685e32 Fix typo in script dir name
Signed-off-by: Ryan Brainard <brainard@heroku.com>
2014-07-11 09:13:00 -07:00
Sam Hanes
699bbe9ca2 Split the domainname out of qualified hostnames.
Docker doesn't like it when a fully qualified hostname is passed in
the `hostname` parameter. When an FQDN is provided with `-h` the
official CLI puts the first component in `hostname` and the rest in
`domainname`. This change replicates that behavior when the user
specifies an FQDN in `hostname` in their `fig.yml`.

Signed-off-by: Sam Hanes <sam@maltera.com>
2014-07-10 20:18:09 -07:00
Ben Firshman
4b890bffde Merge pull request #290 from d11wtq/bugfix/multiplexed_non_tty
BUGFIX: Update dockerpty for non-TTY multiplexing.
2014-07-11 01:44:31 +01:00
Sam Hanes
789e1ba82b Add domainname to allowed container config.
Signed-off-by: Sam Hanes <sam@maltera.com>
2014-07-10 15:56:26 -07:00
Ben Firshman
1a9614c35e Merge pull request #293 from binarin/master
Change working dir through fig.yml
2014-07-10 10:31:57 -07:00
Alexey Lebedeff
d83bdd5164 Change working dir through fig.yml
Closes #144

Signed-off-by: Alexey Lebedeff <binarin@gmail.com>
2014-07-10 21:22:45 +04:00
Ben Firshman
e1a3fc2536 Merge pull request #303 from mozz100/master
Use yaml.safe_load instead of yaml.load
2014-07-10 09:50:51 -07:00
Richard Morrison
251aa7efb6 Use yaml.safe_load instead of yaml.load
http://pyyaml.org/wiki/PyYAMLDocumentation#LoadingYAML

Signed-off-by: Richard Morrison <richard@rmorrison.net>
2014-07-10 15:15:55 +01:00
Aanand Prasad
2924b9997a Merge pull request #302 from orchardup/better-fig-up-output-in-docs
Use concise fig up output in docs
2014-07-09 17:35:41 -07:00
Ben Firshman
2a9aef1332 Use concise fig up output in docs
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-07-09 17:34:39 -07:00
Chris Corbyn
361294d20b Update dockerpty for non-TTY multiplexing.
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-07-05 08:23:21 +00:00
Ben Firshman
9a825c5c35 Merge pull request #279 from orchardup/fix-run-with-no-links
Stop `fig run` starting everything when a service has no links
2014-07-02 15:46:22 +01:00
Aanand Prasad
944e15fa65 Stop fig run starting everything when a service has no links
This was thanks to the semantics of project.up(), which starts everything
if you pass it an empty list of service names. (That logic should
probably be moved out to main.py.)

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2014-07-02 11:51:12 +01:00
Aanand Prasad
d04b1724ec Merge pull request #236 from rail44/feature-support-volumes-from
Support volumes_from option
2014-07-02 10:48:11 +01:00
Satoshi Amemiya
e5916b2fae Support volumes_from option
Signed-off-by: Satoshi Amemiya <rail.sky@gmail.com>
2014-07-02 13:27:54 +09:00
Mark Steve Samson
4f7cbc3812 Support for host address in port bindings (Closes #267) 2014-06-27 15:07:22 +01:00
Aanand Prasad
3c48884dbb Update dockerpty; stub it out in tests
Its current behaviour occasionally causes tests to hang; until this is
resolved, we'll stub it out. We weren't testing the output of 'run'
anyhow (though we should be).
2014-06-27 14:41:36 +01:00
Aanand Prasad
7ec63afae9 Merge pull request #272 from d11wtq/bugfix/race_condition
Update dockerpty to fix discovered race condition.
2014-06-26 13:07:15 +01:00
Chris Corbyn
8c6b516aa0 Update dockerpty to fix discovered race condition.
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-26 11:41:43 +00:00
Aanand Prasad
50c588176c Merge pull request #270 from d11wtq/bugfix/tty_size
Use dockerpty instead for pseudo-tty behaviour (fixes TTY size issue #253 & #97)
2014-06-25 17:00:56 +01:00
Chris Corbyn
3770aac1af Use dockerpty instead for pseudo-tty behaviour.
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-25 15:54:52 +00:00
Aanand Prasad
256dccc554 Merge pull request #268 from orchardup/update-docker-py-0.3.2
Update to docker-py 0.3.2
2014-06-25 14:16:48 +01:00
Ben Firshman
d0f65906ed Update to docker-py 0.3.2
From 59ced5700c

Now only supports Docker 1.0.0 / remote API v1.12.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-06-25 11:56:20 +01:00
Ben Firshman
95aa61cfe5 Merge pull request #246 from d11wtq/feature/auto_start
Feature: `fig up` and `fig run` now start linked containers (closes #31).
2014-06-24 14:46:47 +01:00
Chris Corbyn
247691ca44 Remove auto_start option from fig.yml.
This option is redundant now that services can be started along with links.

Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-21 10:39:36 +00:00
Chris Corbyn
0fc9cc65d1 Rename '--only' => '--no-deps'
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-21 10:30:36 +00:00
Ben Firshman
eb69225444 Merge pull request #209 from orchardup/better-invalid-service-name-error
Better error message when service names are invalid
2014-06-20 12:32:07 +01:00
Aanand Prasad
cafe68a92d Better error message when service names are invalid 2014-06-20 12:10:18 +01:00
Ben Firshman
723cccdae8 Merge pull request #258 from j0hnsmith/support_net_param
add net param support
2014-06-19 12:19:06 +01:00
j0hnsmith
6b8044e92c add net param support
Signed-off-by: Peter Flood <info@whywouldwe.com>
2014-06-19 11:57:55 +01:00
Aanand Prasad
1e7e8202af Merge pull request #257 from orchardup/use-ubuntu-image-to-build-docs
Use ubuntu:13.10 image to build docs
2014-06-18 17:31:23 +01:00
Ben Firshman
c0fdf7bd39 Use ubuntu:13.10 image to build docs
stackbrew/ubuntu and ubuntu are the same thing now.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-06-18 17:26:38 +01:00
Chris Corbyn
1b5bf6e12a Merge remote-tracking branch 'upstream/master' into feature/auto_start
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>

Conflicts:
	tests/fixtures/simple-figfile/fig.yml
	tests/unit/project_test.py
2014-06-12 22:05:51 +00:00
d11wtq
6e485df084 Rename --no-links to --only
Signed-off-by: d11wtq <chris@w3style.co.uk>
2014-06-11 10:50:45 +00:00
Chris Corbyn
e71e82f8ac Add missing fixture file
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-11 10:09:54 +00:00
d11wtq
1d1e23611b Rename --keep-old to --no-recreate
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:51 +00:00
d11wtq
74e067c6e6 Document --keep-old flag in CLI reference
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:50 +00:00
d11wtq
85b9619799 Document 'auto_start' in fig.yml
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:50 +00:00
d11wtq
ab1fbc96c3 Move keep_old check up into Project
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:50 +00:00
d11wtq
a04143e2a7 Remove unused: from __future__ import unicode_literals.
This is not being used and it confuses the Command class. Rather
than try to fix the Command class, I've taken the pragmatic
approach and removed the trigger that confuses it.

Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:50 +00:00
d11wtq
6c4299039a Write integration tests for --keep-old in the CLI
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:49 +00:00
d11wtq
655d347ea2 Write integration tests on new fig run linking behaviour
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:49 +00:00
d11wtq
94a3164248 Re-add missing import for patch
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:49 +00:00
d11wtq
18728a64b9 Write tests for --no-links changes to fig up
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:48 +00:00
d11wtq
d8b0fa294e Add integration tests for Project.up() w/ start_links and keep_old
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:48 +00:00
d11wtq
a6c8319b5d Add integration tests for Service.recreate_containers() with keep_old
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:48 +00:00
d11wtq
5d92f12f8e Add unit tests for include_links in get_services()
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:48 +00:00
d11wtq
c0231bdb70 Rename _prepend_with_links() -> _inject_links()
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:47 +00:00
d11wtq
ac541e208f Remove obsolete method _get_linked_service_names()
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:47 +00:00
d11wtq
3d8ce448b8 Spike: Re-use existing containers for fig run
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:47 +00:00
d11wtq
949df97726 Fix issue with infinite recursion when service_names is empty
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:46 +00:00
d11wtq
14cbe40543 Update doc string to reflect new behaviour.
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:46 +00:00
d11wtq
9dd53ecdaa Fix bug with duplicate service entries in fig up
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:46 +00:00
d11wtq
6bfe5e049d Spike: Implement --no-links for fig up
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:46 +00:00
d11wtq
b672861ffd Spike: Start linked containers on fig run by default
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:45 +00:00
d11wtq
b081077f2b Remove FIXME, as there's nothing to fix :)
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:45 +00:00
d11wtq
13a296049b Update cli integration test for 'auto_start' behaviour
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:45 +00:00
d11wtq
22c531dea7 Add unit tests for Project.get_services()
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:44 +00:00
d11wtq
dfc74e2a77 Write integration test for 'auto_start' behaviour
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:44 +00:00
d11wtq
0c12db06ec Move 'auto_start' option default to Service and add unit tests
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:44 +00:00
d11wtq
edf6b56016 Spike: Add --up option to fig run
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:43 +00:00
d11wtq
8b4ed0c1a8 Spike: Add 'auto_start' option to fig.yml
Signed-off-by: Chris Corbyn <chris@w3style.co.uk>
2014-06-09 01:31:27 +00:00
117 changed files with 4880 additions and 3768 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
.git
venv

3
.gitignore vendored
View File

@@ -1,7 +1,8 @@
*.egg-info
*.pyc
.tox
/build
/dist
/docs/_site
/venv
fig.spec
docker-compose.spec

View File

@@ -1,29 +0,0 @@
language: python
python:
- '2.6'
- '2.7'
env:
global:
- secure: exbot0LTV/0Wic6ElKCrOZmh2ZrieuGwEqfYKf5rVuwu1sLngYRihh+lBL/hTwc79NSu829pbwiWfsQZrXbk/yvaS7avGR0CLDoipyPxlYa2/rfs/o4OdTZqXv0LcFmmd54j5QBMpWU1S+CYOwNkwas57trrvIpPbzWjMtfYzOU=
install:
- pip install .
- pip install -r requirements.txt
- pip install -r requirements-dev.txt
- sudo curl -L -o /usr/local/bin/orchard https://github.com/orchardup/go-orchard/releases/download/2.0.5/linux
- sudo chmod +x /usr/local/bin/orchard
before_script:
- 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then orchard hosts rm -f $TRAVIS_JOB_ID || true; fi'
- 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then orchard hosts create $TRAVIS_JOB_ID; fi'
script:
- nosetests tests/unit
- 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then script/travis-integration; fi'
after_script:
- 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then orchard hosts rm -f $TRAVIS_JOB_ID; fi'
deploy:
provider: pypi
user: orchard
password:
secure: M8UMupCLSsB1hV00Zn6ra8Vg81SCFBpbcRsa0nUw9kgXn9hOCESWYVHTqQ1ksWZOa8z6WMaqYtoosPKXGJQNf0wF/kEVDsMUeaZWOF/PqDkx1EwQ1diVfwlbN4/k0iX+Se7SrZfiWnJiAqiIPqToQipvLlJohqf8WwfPcVvILVE=
on:
tags: true
repo: orchardup/fig

View File

@@ -1,6 +1,150 @@
Change log
==========
1.1.0 (2015-02-25)
------------------
Fig has been renamed to Docker Compose, or just Compose for short. This has several implications for you:
- The command you type is now `docker-compose`, not `fig`.
- You should rename your fig.yml to docker-compose.yml.
- If youre installing via PyPi, the package is now `docker-compose`, so install it with `pip install docker-compose`.
Besides that, theres a lot of new stuff in this release:
- Weve made a few small changes to ensure that Compose will work with Swarm, Dockers new clustering tool (https://github.com/docker/swarm). Eventually you'll be able to point Compose at a Swarm cluster instead of a standalone Docker host and itll run your containers on the cluster with no extra work from you. As Swarm is still developing, integration is rough and lots of Compose features don't work yet.
- `docker-compose run` now has a `--service-ports` flag for exposing ports on the given service. This is useful for e.g. running your webapp with an interactive debugger.
- You can now link to containers outside your app with the `external_links` option in docker-compose.yml.
- You can now prevent `docker-compose up` from automatically building images with the `--no-build` option. This will make fewer API calls and run faster.
- If you dont specify a tag when using the `image` key, Compose will default to the `latest` tag, rather than pulling all tags.
- `docker-compose kill` now supports the `-s` flag, allowing you to specify the exact signal you want to send to a services containers.
- docker-compose.yml now has an `env_file` key, analogous to `docker run --env-file`, letting you specify multiple environment variables in a separate file. This is great if you have a lot of them, or if you want to keep sensitive information out of version control.
- docker-compose.yml now supports the `dns_search`, `cap_add`, `cap_drop`, `cpu_shares` and `restart` options, analogous to `docker run`s `--dns-search`, `--cap-add`, `--cap-drop`, `--cpu-shares` and `--restart` options.
- Compose now ships with Bash tab completion - see the installation and usage docs at https://github.com/docker/compose/blob/1.1.0/docs/completion.md
- A number of bugs have been fixed - see the milestone for details: https://github.com/docker/compose/issues?q=milestone%3A1.1.0+
Thanks @dnephin, @squebe, @jbalonso, @raulcd, @benlangfield, @albers, @ggtools, @bersace, @dtenenba, @petercv, @drewkett, @TFenby, @paulRbr, @Aigeruth and @salehe!
1.0.1 (2014-11-04)
------------------
- Added an `--allow-insecure-ssl` option to allow `fig up`, `fig run` and `fig pull` to pull from insecure registries.
- Fixed `fig run` not showing output in Jenkins.
- Fixed a bug where Fig couldn't build Dockerfiles with ADD statements pointing at URLs.
1.0.0 (2014-10-16)
------------------
The highlights:
- [Fig has joined Docker.](https://www.orchardup.com/blog/orchard-is-joining-docker) Fig will continue to be maintained, but we'll also be incorporating the best bits of Fig into Docker itself.
This means the GitHub repository has moved to [https://github.com/docker/fig](https://github.com/docker/fig) and our IRC channel is now #docker-fig on Freenode.
- Fig can be used with the [official Docker OS X installer](https://docs.docker.com/installation/mac/). Boot2Docker will mount the home directory from your host machine so volumes work as expected.
- Fig supports Docker 1.3.
- It is now possible to connect to the Docker daemon using TLS by using the `DOCKER_CERT_PATH` and `DOCKER_TLS_VERIFY` environment variables.
- There is a new `fig port` command which outputs the host port binding of a service, in a similar way to `docker port`.
- There is a new `fig pull` command which pulls the latest images for a service.
- There is a new `fig restart` command which restarts a service's containers.
- Fig creates multiple containers in service by appending a number to the service name (e.g. `db_1`, `db_2`, etc). As a convenience, Fig will now give the first container an alias of the service name (e.g. `db`).
This link alias is also a valid hostname and added to `/etc/hosts` so you can connect to linked services using their hostname. For example, instead of resolving the environment variables `DB_PORT_5432_TCP_ADDR` and `DB_PORT_5432_TCP_PORT`, you could just use the hostname `db` and port `5432` directly.
- Volume definitions now support `ro` mode, expanding `~` and expanding environment variables.
- `.dockerignore` is supported when building.
- The project name can be set with the `FIG_PROJECT_NAME` environment variable.
- The `--env` and `--entrypoint` options have been added to `fig run`.
- The Fig binary for Linux is now linked against an older version of glibc so it works on CentOS 6 and Debian Wheezy.
Other things:
- `fig ps` now works on Jenkins and makes fewer API calls to the Docker daemon.
- `--verbose` displays more useful debugging output.
- When starting a service where `volumes_from` points to a service without any containers running, that service will now be started.
- Lots of docs improvements. Notably, environment variables are documented and official repositories are used throughout.
Thanks @dnephin, @d11wtq, @marksteve, @rubbish, @jbalonso, @timfreund, @alunduil, @mieciu, @shuron, @moss, @suzaku and @chmouel! Whew.
0.5.2 (2014-07-28)
------------------
- Added a `--no-cache` option to `fig build`, which bypasses the cache just like `docker build --no-cache`.
- Fixed the `dns:` fig.yml option, which was causing fig to error out.
- Fixed a bug where fig couldn't start under Python 2.6.
- Fixed a log-streaming bug that occasionally caused fig to exit.
Thanks @dnephin and @marksteve!
0.5.1 (2014-07-11)
------------------
- If a service has a command defined, `fig run [service]` with no further arguments will run it.
- The project name now defaults to the directory containing fig.yml, not the current working directory (if they're different)
- `volumes_from` now works properly with containers as well as services
- Fixed a race condition when recreating containers in `fig up`
Thanks @ryanbrainard and @d11wtq!
0.5.0 (2014-07-11)
------------------
- Fig now starts links when you run `fig run` or `fig up`.
For example, if you have a `web` service which depends on a `db` service, `fig run web ...` will start the `db` service.
- Environment variables can now be resolved from the environment that Fig is running in. Just specify it as a blank variable in your `fig.yml` and, if set, it'll be resolved:
```
environment:
RACK_ENV: development
SESSION_SECRET:
```
- `volumes_from` is now supported in `fig.yml`. All of the volumes from the specified services and containers will be mounted:
```
volumes_from:
- service_name
- container_name
```
- A host address can now be specified in `ports`:
```
ports:
- "0.0.0.0:8000:8000"
- "127.0.0.1:8001:8001"
```
- The `net` and `workdir` options are now supported in `fig.yml`.
- The `hostname` option now works in the same way as the Docker CLI, splitting out into a `domainname` option.
- TTY behaviour is far more robust, and resizes are supported correctly.
- Load YAML files safely.
Thanks to @d11wtq, @ryanbrainard, @rail44, @j0hnsmith, @binarin, @Elemecca, @mozz100 and @marksteve for their help with this release!
0.4.2 (2014-06-18)
------------------

View File

@@ -1,17 +1,26 @@
# Contributing to Fig
# Contributing to Compose
Compose is a part of the Docker project, and follows the same rules and principles. Take a read of [Docker's contributing guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) to get an overview.
## TL;DR
Pull requests will need:
- Tests
- Documentation
- [To be signed off](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work)
- A logical series of [well written commits](https://github.com/alphagov/styleguides/blob/master/git.md)
## Development environment
If you're looking contribute to [Fig](http://orchardup.github.io/fig/)
If you're looking contribute to Compose
but you're new to the project or maybe even to Python, here are the steps
that should get you started.
1. Fork [https://github.com/orchardup/fig](https://github.com/orchardup/fig) to your username. kvz in this example.
1. Clone your forked repository locally `git clone git@github.com:kvz/fig.git`.
1. Enter the local directory `cd fig`.
1. Set up a development environment `python setup.py develop`. That will install the dependencies and set up a symlink from your `fig` executable to the checkout of the repo. So from any of your fig projects, `fig` now refers to your development project. Time to start hacking : )
1. Works for you? Run the test suite via `./scripts/test` to verify it won't break other usecases.
1. All good? Commit and push to GitHub, and submit a pull request.
1. Fork [https://github.com/docker/compose](https://github.com/docker/compose) to your username.
1. Clone your forked repository locally `git clone git@github.com:yourusername/compose.git`.
1. Enter the local directory `cd compose`.
1. Set up a development environment by running `python setup.py develop`. This will install the dependencies and set up a symlink from your `docker-compose` executable to the checkout of the repository. When you now run `docker-compose` from anywhere on your machine, it will run your development version of Compose.
## Running the test suite
@@ -29,47 +38,26 @@ OS X:
Note that this only works on Mountain Lion, not Mavericks, due to a [bug in PyInstaller](http://www.pyinstaller.org/ticket/807).
## Sign your work
## Release process
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from [developercertificate.org](http://developercertificate.org/)):
1. Open pull request that:
Developer's Certificate of Origin 1.1
- Updates the version in `compose/__init__.py`
- Updates the binary URL in `docs/install.md`
- Updates the script URL in `docs/completion.md`
- Adds release notes to `CHANGES.md`
By making a contribution to this project, I certify that:
2. Create unpublished GitHub release with release notes
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
3. Build Linux version on any Docker host with `script/build-linux` and attach to release
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
4. Build OS X version on Mountain Lion with `script/build-osx` and attach to release as `docker-compose-Darwin-x86_64` and `docker-compose-Linux-x86_64`.
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
5. Publish GitHub release, creating tag
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
6. Update website with `script/deploy-docs`
then you just add a line saying
Signed-off-by: Random J Developer <random@developer.example.org>
using your real name (sorry, no pseudonyms or anonymous contributions.)
The easiest way to do this is to use the `--signoff` flag when committing. E.g.:
$ git commit --signoff
7. Upload PyPi package
$ git checkout $VERSION
$ python setup.py sdist upload

View File

@@ -1,11 +1,17 @@
FROM orchardup/python:2.7
ADD requirements.txt /code/
FROM debian:wheezy
RUN apt-get update -qq && apt-get install -qy python python-pip python-dev git && apt-get clean
RUN useradd -d /home/user -m -s /bin/bash user
WORKDIR /code/
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD requirements-dev.txt /code/
RUN pip install -r requirements-dev.txt
ADD . /code/
RUN python setup.py develop
RUN useradd -d /home/user -m -s /bin/bash user
RUN python setup.py install
RUN chown -R user /code/
USER user
ENTRYPOINT ["/usr/local/bin/docker-compose"]

View File

@@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS
Copyright 2014 Orchard Laboratories Ltd.
Copyright 2014 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

4
MAINTAINERS Normal file
View File

@@ -0,0 +1,4 @@
Aanand Prasad <aanand.prasad@gmail.com> (@aanand)
Ben Firshman <ben@firshman.co.uk> (@bfirsh)
Chris Corbyn <chris@w3style.co.uk> (@d11wtq)
Daniel Nephin <dnephin@gmail.com> (@dnephin)

View File

@@ -4,7 +4,7 @@ include requirements.txt
include requirements-dev.txt
include tox.ini
include *.md
recursive-exclude tests *
recursive-include tests *
global-exclude *.pyc
global-exclude *.pyo
global-exclude *.un~

View File

@@ -1,20 +1,34 @@
Fig
===
Docker Compose
==============
[![Build Status](https://travis-ci.org/orchardup/fig.svg?branch=master)](https://travis-ci.org/orchardup/fig)
[![PyPI version](https://badge.fury.io/py/fig.png)](http://badge.fury.io/py/fig)
[![wercker status](https://app.wercker.com/status/d5dbac3907301c3d5ce735e2d5e95a5b/s/master "wercker status")](https://app.wercker.com/project/bykey/d5dbac3907301c3d5ce735e2d5e95a5b)
Fast, isolated development environments using Docker.
*(Previously known as Fig)*
Define your app's environment with Docker so it can be reproduced anywhere:
Compose is a tool for defining and running complex applications with Docker.
With Compose, you define a multi-container application in a single file, then
spin your application up in a single command which does everything that needs to
be done to get it running.
FROM orchardup/python:2.7
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
CMD python app.py
Compose is great for development environments, staging servers, and CI. We don't
recommend that you use it in production yet.
Define the services that make up your app so they can be run together in an isolated environment:
Using Compose is basically a three-step process.
First, you define your app's environment with a `Dockerfile` so it can be
reproduced anywhere:
```Dockerfile
FROM python:2.7
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code
CMD python app.py
```
Next, you define the services that make up your app in `docker-compose.yml` so
they can be run together in an isolated environment:
```yaml
web:
@@ -23,27 +37,20 @@ web:
- db
ports:
- "8000:8000"
- "49100:22"
db:
image: orchardup/postgresql
image: postgres
```
(No more installing Postgres on your laptop!)
Lastly, run `docker-compose up` and Compose will start and run your entire app.
Then type `fig up`, and Fig will start and run your entire app:
Compose has commands for managing the whole lifecycle of your application:
![example fig run](https://orchardup.com/static/images/fig-example-large.gif)
There are commands to:
- start, stop and rebuild services
- view the status of running services
- tail running services' log output
- run a one-off command on a service
Fig is a project from [Orchard](https://orchardup.com), a Docker hosting service. [Follow us on Twitter](https://twitter.com/orchardup) to keep up to date with Fig and other Docker news.
* Start, stop and rebuild services
* View the status of running services
* Stream the log output of running services
* Run a one-off command on a service
Installation and documentation
------------------------------
Full documentation is available on [Fig's website](http://orchardup.github.io/fig/).
Full documentation is available on [Fig's website](http://www.fig.sh/).

28
ROADMAP.md Normal file
View File

@@ -0,0 +1,28 @@
# Roadmap
## More than just development environments
Over time we will extend Compose's remit to cover test, staging and production environments. This is not a simple task, and will take many incremental improvements such as:
- Composes brute-force “delete and recreate everything” approach is great for dev and testing, but it not sufficient for production environments. You should be able to define a "desired" state that Compose will intelligently converge to.
- It should be possible to partially modify the config file for different environments (dev/test/staging/prod), passing in e.g. custom ports or volume mount paths. ([#426](https://github.com/docker/fig/issues/426))
- Compose should recommend a technique for zero-downtime deploys.
## Integration with Swarm
Compose should integrate really well with Swarm so you can take an application you've developed on your laptop and run it on a Swarm cluster.
The current state of integration is documented in [SWARM.md](SWARM.md).
## Applications spanning multiple teams
Compose works well for applications that are in a single repository and depend on services that are hosted on Docker Hub. If your application depends on another application within your organisation, Compose doesn't work as well.
There are several ideas about how this could work, such as [including external files](https://github.com/docker/fig/issues/318).
## An even better tool for development environments
Compose is a great tool for development environments, but it could be even better. For example:
- [Compose could watch your code and automatically kick off builds when something changes.](https://github.com/docker/fig/issues/184)
- It should be possible to define hostnames for containers which work from the host machine, e.g. “mywebcontainer.local”. This is needed by apps comprising multiple web services which generate links to one another (e.g. a frontend website and a separate admin webapp)

51
SWARM.md Normal file
View File

@@ -0,0 +1,51 @@
Docker Compose/Swarm integration
================================
Eventually, Compose and Swarm aim to have full integration, meaning you can point a Compose app at a Swarm cluster and have it all just work as if you were using a single Docker host.
However, the current extent of integration is minimal: Compose can create containers on a Swarm cluster, but the majority of Compose apps wont work out of the box unless all containers are scheduled on one host, defeating much of the purpose of using Swarm in the first place.
Still, Compose and Swarm can be useful in a “batch processing” scenario (where a large number of containers need to be spun up and down to do independent computation) or a “shared cluster” scenario (where multiple teams want to deploy apps on a cluster without worrying about where to put them).
A number of things need to happen before full integration is achieved, which are documented below.
Re-deploying containers with `docker-compose up`
------------------------------------------------
Repeated invocations of `docker-compose up` will not work reliably when used against a Swarm cluster because of an under-the-hood design problem; [this will be fixed](https://github.com/docker/fig/pull/972) in the next version of Compose. For now, containers must be completely removed and re-created:
$ docker-compose kill
$ docker-compose rm --force
$ docker-compose up
Links and networking
--------------------
The primary thing stopping multi-container apps from working seamlessly on Swarm is getting them to talk to one another: enabling private communication between containers on different hosts hasnt been solved in a non-hacky way.
Long-term, networking is [getting overhauled](https://github.com/docker/docker/issues/9983) in such a way that itll fit the multi-host model much better. For now, containers on different hosts cannot be linked. In the next version of Compose, linked services will be automatically scheduled on the same host; for now, this must be done manually (see “Co-scheduling containers” below).
`volumes_from` and `net: container`
-----------------------------------
For containers to share volumes or a network namespace, they must be scheduled on the same host - this is, after all, inherent to how both volumes and network namespaces work. In the next version of Compose, this co-scheduling will be automatic whenever `volumes_from` or `net: "container:..."` is specified; for now, containers which share volumes or a network namespace must be co-scheduled manually (see “Co-scheduling containers” below).
Co-scheduling containers
------------------------
For now, containers can be manually scheduled on the same host using Swarms [affinity filters](https://github.com/docker/swarm/blob/master/scheduler/filter/README.md#affinity-filter). Heres a simple example:
```yaml
web:
image: my-web-image
links: ["db"]
environment:
- "affinity:container==myproject_db_*"
db:
image: postgres
```
Here, we express an affinity filter on all web containers, saying that each one must run alongside a container whose name begins with `myproject_db_`.
- `myproject` is the common prefix Compose gives to all containers in your project, which is either generated from the name of the current directory or specified with `-p` or the `DOCKER_COMPOSE_PROJECT_NAME` environment variable.
- `*` is a wildcard, which works just like filename wildcards in a Unix shell.

3
bin/docker-compose Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env python
from compose.cli.main import main
main()

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
from fig.cli.main import main
main()

4
compose/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
from __future__ import unicode_literals
from .service import Service # noqa:flake8
__version__ = '1.1.0'

140
compose/cli/command.py Normal file
View File

@@ -0,0 +1,140 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from requests.exceptions import ConnectionError, SSLError
import logging
import os
import re
import yaml
import six
from ..project import Project
from ..service import ConfigError
from .docopt_command import DocoptCommand
from .utils import call_silently, is_mac, is_ubuntu
from .docker_client import docker_client
from . import verbose_proxy
from . import errors
from .. import __version__
log = logging.getLogger(__name__)
class Command(DocoptCommand):
base_dir = '.'
def dispatch(self, *args, **kwargs):
try:
super(Command, self).dispatch(*args, **kwargs)
except SSLError, e:
raise errors.UserError('SSL error: %s' % e)
except ConnectionError:
if call_silently(['which', 'docker']) != 0:
if is_mac():
raise errors.DockerNotFoundMac()
elif is_ubuntu():
raise errors.DockerNotFoundUbuntu()
else:
raise errors.DockerNotFoundGeneric()
elif call_silently(['which', 'boot2docker']) == 0:
raise errors.ConnectionErrorBoot2Docker()
else:
raise errors.ConnectionErrorGeneric(self.get_client().base_url)
def perform_command(self, options, handler, command_options):
if options['COMMAND'] == 'help':
# Skip looking up the compose file.
handler(None, command_options)
return
if 'FIG_FILE' in os.environ:
log.warn('The FIG_FILE environment variable is deprecated.')
log.warn('Please use COMPOSE_FILE instead.')
explicit_config_path = options.get('--file') or os.environ.get('COMPOSE_FILE') or os.environ.get('FIG_FILE')
project = self.get_project(
self.get_config_path(explicit_config_path),
project_name=options.get('--project-name'),
verbose=options.get('--verbose'))
handler(project, command_options)
def get_client(self, verbose=False):
client = docker_client()
if verbose:
version_info = six.iteritems(client.version())
log.info("Compose version %s", __version__)
log.info("Docker base_url: %s", client.base_url)
log.info("Docker version: %s",
", ".join("%s=%s" % item for item in version_info))
return verbose_proxy.VerboseProxy('docker', client)
return client
def get_config(self, config_path):
try:
with open(config_path, 'r') as fh:
return yaml.safe_load(fh)
except IOError as e:
raise errors.UserError(six.text_type(e))
def get_project(self, config_path, project_name=None, verbose=False):
try:
return Project.from_config(
self.get_project_name(config_path, project_name),
self.get_config(config_path),
self.get_client(verbose=verbose))
except ConfigError as e:
raise errors.UserError(six.text_type(e))
def get_project_name(self, config_path, project_name=None):
def normalize_name(name):
return re.sub(r'[^a-z0-9]', '', name.lower())
if 'FIG_PROJECT_NAME' in os.environ:
log.warn('The FIG_PROJECT_NAME environment variable is deprecated.')
log.warn('Please use COMPOSE_PROJECT_NAME instead.')
project_name = project_name or os.environ.get('COMPOSE_PROJECT_NAME') or os.environ.get('FIG_PROJECT_NAME')
if project_name is not None:
return normalize_name(project_name)
project = os.path.basename(os.path.dirname(os.path.abspath(config_path)))
if project:
return normalize_name(project)
return 'default'
def get_config_path(self, file_path=None):
if file_path:
return os.path.join(self.base_dir, file_path)
supported_filenames = [
'docker-compose.yml',
'docker-compose.yaml',
'fig.yml',
'fig.yaml',
]
def expand(filename):
return os.path.join(self.base_dir, filename)
candidates = [filename for filename in supported_filenames if os.path.exists(expand(filename))]
if len(candidates) == 0:
raise errors.ComposeFileNotFound(supported_filenames)
winner = candidates[0]
if len(candidates) > 1:
log.warning("Found multiple config files with supported names: %s", ", ".join(candidates))
log.warning("Using %s\n", winner)
if winner == 'docker-compose.yaml':
log.warning("Please be aware that .yml is the expected extension "
"in most cases, and using .yaml can cause compatibility "
"issues in future.\n")
if winner.startswith("fig."):
log.warning("%s is deprecated and will not be supported in future. "
"Please rename your config file to docker-compose.yml\n" % winner)
return expand(winner)

View File

@@ -0,0 +1,35 @@
from docker import Client
from docker import tls
import ssl
import os
def docker_client():
"""
Returns a docker-py client configured using environment variables
according to the same logic as the official Docker client.
"""
cert_path = os.environ.get('DOCKER_CERT_PATH', '')
if cert_path == '':
cert_path = os.path.join(os.environ.get('HOME', ''), '.docker')
base_url = os.environ.get('DOCKER_HOST')
tls_config = None
if os.environ.get('DOCKER_TLS_VERIFY', '') != '':
parts = base_url.split('://', 1)
base_url = '%s://%s' % ('https', parts[1])
client_cert = (os.path.join(cert_path, 'cert.pem'), os.path.join(cert_path, 'key.pem'))
ca_cert = os.path.join(cert_path, 'ca.pem')
tls_config = tls.TLSConfig(
ssl_version=ssl.PROTOCOL_TLSv1,
verify=True,
assert_hostname=False,
client_cert=client_cert,
ca_cert=ca_cert,
)
timeout = int(os.environ.get('DOCKER_CLIENT_TIMEOUT', 60))
return Client(base_url=base_url, tls=tls_config, version='1.14', timeout=timeout)

View File

@@ -23,7 +23,7 @@ class DocoptCommand(object):
def dispatch(self, argv, global_options):
self.perform_command(*self.parse(argv, global_options))
def perform_command(self, options, command, handler, command_options):
def perform_command(self, options, handler, command_options):
handler(command_options)
def parse(self, argv, global_options):
@@ -43,7 +43,7 @@ class DocoptCommand(object):
raise NoSuchCommand(command, self)
command_options = docopt_full_help(docstring, options['ARGS'], options_first=True)
return (options, command, handler, command_options)
return options, handler, command_options
class NoSuchCommand(Exception):

View File

@@ -9,6 +9,8 @@ class UserError(Exception):
def __unicode__(self):
return self.msg
__str__ = __unicode__
class DockerNotFoundMac(UserError):
def __init__(self):
@@ -37,10 +39,10 @@ class DockerNotFoundGeneric(UserError):
""")
class ConnectionErrorDockerOSX(UserError):
class ConnectionErrorBoot2Docker(UserError):
def __init__(self):
super(ConnectionErrorDockerOSX, self).__init__("""
Couldn't connect to Docker daemon - you might need to run `docker-osx shell`.
super(ConnectionErrorBoot2Docker, self).__init__("""
Couldn't connect to Docker daemon - you might need to run `boot2docker up`.
""")
@@ -53,8 +55,10 @@ class ConnectionErrorGeneric(UserError):
""" % url)
class FigFileNotFound(UserError):
def __init__(self, filename):
super(FigFileNotFound, self).__init__("""
Can't find %s. Are you in the right directory?
""" % filename)
class ComposeFileNotFound(UserError):
def __init__(self, supported_filenames):
super(ComposeFileNotFound, self).__init__("""
Can't find a suitable configuration file. Are you in the right directory?
Supported filenames: %s
""" % ", ".join(supported_filenames))

View File

@@ -4,11 +4,17 @@ import os
import texttable
def get_tty_width():
tty_size = os.popen('stty size', 'r').read().split()
if len(tty_size) != 2:
return 80
_, width = tty_size
return int(width)
class Formatter(object):
def table(self, headers, rows):
height, width = os.popen('stty size', 'r').read().split()
table = texttable.Texttable(max_width=width)
table = texttable.Texttable(max_width=get_tty_width())
table.set_cols_dtype(['t' for h in headers])
table.add_rows([headers] + rows)
table.set_deco(table.HEADER)

View File

@@ -10,11 +10,11 @@ from .utils import split_buffer
class LogPrinter(object):
def __init__(self, containers, attach_params=None, output=sys.stdout):
def __init__(self, containers, attach_params=None, output=sys.stdout, monochrome=False):
self.containers = containers
self.attach_params = attach_params or {}
self.prefix_width = self._calculate_prefix_width(containers)
self.generators = self._make_log_generators()
self.generators = self._make_log_generators(monochrome)
self.output = output
def run(self):
@@ -35,12 +35,18 @@ class LogPrinter(object):
prefix_width = max(prefix_width, len(container.name_without_project))
return prefix_width
def _make_log_generators(self):
def _make_log_generators(self, monochrome):
color_fns = cycle(colors.rainbow())
generators = []
def no_color(text):
return text
for container in self.containers:
color_fn = color_fns.next()
if monochrome:
color_fn = no_color
else:
color_fn = color_fns.next()
generators.append(self._make_log_generator(container, color_fn))
return generators

467
compose/cli/main.py Normal file
View File

@@ -0,0 +1,467 @@
from __future__ import print_function
from __future__ import unicode_literals
import logging
import sys
import re
import signal
from operator import attrgetter
from inspect import getdoc
import dockerpty
from .. import __version__
from ..project import NoSuchService, ConfigurationError
from ..service import BuildError, CannotBeScaledError
from .command import Command
from .formatter import Formatter
from .log_printer import LogPrinter
from .utils import yesno
from docker.errors import APIError
from .errors import UserError
from .docopt_command import NoSuchCommand
log = logging.getLogger(__name__)
def main():
setup_logging()
try:
command = TopLevelCommand()
command.sys_dispatch()
except KeyboardInterrupt:
log.error("\nAborting.")
sys.exit(1)
except (UserError, NoSuchService, ConfigurationError) as e:
log.error(e.msg)
sys.exit(1)
except NoSuchCommand as e:
log.error("No such command: %s", e.command)
log.error("")
log.error("\n".join(parse_doc_section("commands:", getdoc(e.supercommand))))
sys.exit(1)
except APIError as e:
log.error(e.explanation)
sys.exit(1)
except BuildError as e:
log.error("Service '%s' failed to build: %s" % (e.service.name, e.reason))
sys.exit(1)
def setup_logging():
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setFormatter(logging.Formatter())
console_handler.setLevel(logging.INFO)
root_logger = logging.getLogger()
root_logger.addHandler(console_handler)
root_logger.setLevel(logging.DEBUG)
# Disable requests logging
logging.getLogger("requests").propagate = False
# stolen from docopt master
def parse_doc_section(name, source):
pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)',
re.IGNORECASE | re.MULTILINE)
return [s.strip() for s in pattern.findall(source)]
class TopLevelCommand(Command):
"""Fast, isolated development environments using Docker.
Usage:
docker-compose [options] [COMMAND] [ARGS...]
docker-compose -h|--help
Options:
--verbose Show more output
--version Print version and exit
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
-p, --project-name NAME Specify an alternate project name (default: directory name)
Commands:
build Build or rebuild services
help Get help on a command
kill Kill containers
logs View output from containers
port Print the public port for a port binding
ps List containers
pull Pulls service images
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
restart Restart services
up Create and start containers
"""
def docopt_options(self):
options = super(TopLevelCommand, self).docopt_options()
options['version'] = "docker-compose %s" % __version__
return options
def build(self, project, options):
"""
Build or rebuild services.
Services are built once and then tagged as `project_service`,
e.g. `composetest_db`. If you change a service's `Dockerfile` or the
contents of its build directory, you can run `compose build` to rebuild it.
Usage: build [options] [SERVICE...]
Options:
--no-cache Do not use cache when building the image.
"""
no_cache = bool(options.get('--no-cache', False))
project.build(service_names=options['SERVICE'], no_cache=no_cache)
def help(self, project, options):
"""
Get help on a command.
Usage: help COMMAND
"""
command = options['COMMAND']
if not hasattr(self, command):
raise NoSuchCommand(command, self)
raise SystemExit(getdoc(getattr(self, command)))
def kill(self, project, options):
"""
Force stop service containers.
Usage: kill [options] [SERVICE...]
Options:
-s SIGNAL SIGNAL to send to the container.
Default signal is SIGKILL.
"""
signal = options.get('-s', 'SIGKILL')
project.kill(service_names=options['SERVICE'], signal=signal)
def logs(self, project, options):
"""
View output from containers.
Usage: logs [options] [SERVICE...]
Options:
--no-color Produce monochrome output.
"""
containers = project.containers(service_names=options['SERVICE'], stopped=True)
monochrome = options['--no-color']
print("Attaching to", list_containers(containers))
LogPrinter(containers, attach_params={'logs': True}, monochrome=monochrome).run()
def port(self, project, options):
"""
Print the public port for a port binding.
Usage: port [options] SERVICE PRIVATE_PORT
Options:
--protocol=proto tcp or udp (defaults to tcp)
--index=index index of the container if there are multiple
instances of a service (defaults to 1)
"""
service = project.get_service(options['SERVICE'])
try:
container = service.get_container(number=options.get('--index') or 1)
except ValueError as e:
raise UserError(str(e))
print(container.get_local_port(
options['PRIVATE_PORT'],
protocol=options.get('--protocol') or 'tcp') or '')
def ps(self, project, options):
"""
List containers.
Usage: ps [options] [SERVICE...]
Options:
-q Only display IDs
"""
containers = sorted(
project.containers(service_names=options['SERVICE'], stopped=True) +
project.containers(service_names=options['SERVICE'], one_off=True),
key=attrgetter('name'))
if options['-q']:
for container in containers:
print(container.id)
else:
headers = [
'Name',
'Command',
'State',
'Ports',
]
rows = []
for container in containers:
command = container.human_readable_command
if len(command) > 30:
command = '%s ...' % command[:26]
rows.append([
container.name,
command,
container.human_readable_state,
container.human_readable_ports,
])
print(Formatter().table(headers, rows))
def pull(self, project, options):
"""
Pulls images for services.
Usage: pull [options] [SERVICE...]
Options:
--allow-insecure-ssl Allow insecure connections to the docker
registry
"""
insecure_registry = options['--allow-insecure-ssl']
project.pull(
service_names=options['SERVICE'],
insecure_registry=insecure_registry
)
def rm(self, project, options):
"""
Remove stopped service containers.
Usage: rm [options] [SERVICE...]
Options:
--force Don't ask to confirm removal
-v Remove volumes associated with containers
"""
all_containers = project.containers(service_names=options['SERVICE'], stopped=True)
stopped_containers = [c for c in all_containers if not c.is_running]
if len(stopped_containers) > 0:
print("Going to remove", list_containers(stopped_containers))
if options.get('--force') \
or yesno("Are you sure? [yN] ", default=False):
project.remove_stopped(
service_names=options['SERVICE'],
v=options.get('-v', False)
)
else:
print("No stopped containers")
def run(self, project, options):
"""
Run a one-off command on a service.
For example:
$ docker-compose run web python manage.py shell
By default, linked services will be started, unless they are already
running. If you do not want to start linked services, use
`docker-compose run --no-deps SERVICE COMMAND [ARGS...]`.
Usage: run [options] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]
Options:
--allow-insecure-ssl Allow insecure connections to the docker
registry
-d Detached mode: Run container in the background, print
new container name.
--entrypoint CMD Override the entrypoint of the image.
-e KEY=VAL Set an environment variable (can be used multiple times)
--no-deps Don't start linked services.
--rm Remove container after run. Ignored in detached mode.
--service-ports Run command with the service's ports enabled and mapped
to the host.
-T Disable pseudo-tty allocation. By default `docker-compose run`
allocates a TTY.
"""
service = project.get_service(options['SERVICE'])
insecure_registry = options['--allow-insecure-ssl']
if not options['--no-deps']:
deps = service.get_linked_names()
if len(deps) > 0:
project.up(
service_names=deps,
start_links=True,
recreate=False,
insecure_registry=insecure_registry,
detach=options['-d']
)
tty = True
if options['-d'] or options['-T'] or not sys.stdin.isatty():
tty = False
if options['COMMAND']:
command = [options['COMMAND']] + options['ARGS']
else:
command = service.options.get('command')
container_options = {
'command': command,
'tty': tty,
'stdin_open': not options['-d'],
'detach': options['-d'],
}
if options['-e']:
for option in options['-e']:
if 'environment' not in service.options:
service.options['environment'] = {}
k, v = option.split('=', 1)
service.options['environment'][k] = v
if options['--entrypoint']:
container_options['entrypoint'] = options.get('--entrypoint')
container = service.create_container(
one_off=True,
insecure_registry=insecure_registry,
**container_options
)
service_ports = None
if options['--service-ports']:
service_ports = service.options['ports']
if options['-d']:
service.start_container(container, ports=service_ports, one_off=True)
print(container.name)
else:
service.start_container(container, ports=service_ports, one_off=True)
dockerpty.start(project.client, container.id, interactive=not options['-T'])
exit_code = container.wait()
if options['--rm']:
log.info("Removing %s..." % container.name)
project.client.remove_container(container.id)
sys.exit(exit_code)
def scale(self, project, options):
"""
Set number of containers to run for a service.
Numbers are specified in the form `service=num` as arguments.
For example:
$ docker-compose scale web=2 worker=3
Usage: scale [SERVICE=NUM...]
"""
for s in options['SERVICE=NUM']:
if '=' not in s:
raise UserError('Arguments to scale should be in the form service=num')
service_name, num = s.split('=', 1)
try:
num = int(num)
except ValueError:
raise UserError('Number of containers for service "%s" is not a '
'number' % service_name)
try:
project.get_service(service_name).scale(num)
except CannotBeScaledError:
raise UserError(
'Service "%s" cannot be scaled because it specifies a port '
'on the host. If multiple containers for this service were '
'created, the port would clash.\n\nRemove the ":" from the '
'port definition in docker-compose.yml so Docker can choose a random '
'port for each container.' % service_name)
def start(self, project, options):
"""
Start existing containers.
Usage: start [SERVICE...]
"""
project.start(service_names=options['SERVICE'])
def stop(self, project, options):
"""
Stop running containers without removing them.
They can be started again with `docker-compose start`.
Usage: stop [SERVICE...]
"""
project.stop(service_names=options['SERVICE'])
def restart(self, project, options):
"""
Restart running containers.
Usage: restart [SERVICE...]
"""
project.restart(service_names=options['SERVICE'])
def up(self, project, options):
"""
Build, (re)create, start and attach to containers for a service.
By default, `docker-compose up` will aggregate the output of each container, and
when it exits, all containers will be stopped. If you run `docker-compose up -d`,
it'll start the containers in the background and leave them running.
If there are existing containers for a service, `docker-compose up` will stop
and recreate them (preserving mounted volumes with volumes-from),
so that changes in `docker-compose.yml` are picked up. If you do not want existing
containers to be recreated, `docker-compose up --no-recreate` will re-use existing
containers.
Usage: up [options] [SERVICE...]
Options:
--allow-insecure-ssl Allow insecure connections to the docker
registry
-d Detached mode: Run containers in the background,
print new container names.
--no-color Produce monochrome output.
--no-deps Don't start linked services.
--no-recreate If containers already exist, don't recreate them.
--no-build Don't build an image, even if it's missing
"""
insecure_registry = options['--allow-insecure-ssl']
detached = options['-d']
monochrome = options['--no-color']
start_links = not options['--no-deps']
recreate = not options['--no-recreate']
service_names = options['SERVICE']
project.up(
service_names=service_names,
start_links=start_links,
recreate=recreate,
insecure_registry=insecure_registry,
detach=options['-d'],
do_build=not options['--no-build'],
)
to_attach = [c for s in project.get_services(service_names) for c in s.containers()]
if not detached:
print("Attaching to", list_containers(to_attach))
log_printer = LogPrinter(to_attach, attach_params={"logs": True}, monochrome=monochrome)
try:
log_printer.run()
finally:
def handler(signal, frame):
project.kill(service_names=service_names)
sys.exit(0)
signal.signal(signal.SIGINT, handler)
print("Gracefully stopping... (press Ctrl+C again to force)")
project.stop(service_names=service_names)
def list_containers(containers):
return ", ".join(c.name for c in containers)

View File

@@ -7,25 +7,6 @@ import subprocess
import platform
def cached_property(f):
"""
returns a cached property that is calculated by function f
http://code.activestate.com/recipes/576563-cached-property/
"""
def get(self):
try:
return self._property_cache[f]
except AttributeError:
self._property_cache = {}
x = self._property_cache[f] = f(self)
return x
except KeyError:
x = self._property_cache[f] = f(self)
return x
return property(get)
def yesno(prompt, default=None):
"""
Prompt the user for a yes or no.
@@ -65,11 +46,11 @@ def prettydate(d):
elif s < 120:
return '1 minute ago'
elif s < 3600:
return '{0} minutes ago'.format(s/60)
return '{0} minutes ago'.format(s / 60)
elif s < 7200:
return '1 hour ago'
else:
return '{0} hours ago'.format(s/3600)
return '{0} hours ago'.format(s / 3600)
def mkdir(path, permissions=0o700):
@@ -81,10 +62,6 @@ def mkdir(path, permissions=0o700):
return path
def docker_url():
return os.environ.get('DOCKER_HOST')
def split_buffer(reader, separator):
"""
Given a generator which yields strings and a separator string,
@@ -103,8 +80,8 @@ def split_buffer(reader, separator):
index = buffered.find(separator)
if index == -1:
break
yield buffered[:index+1]
buffered = buffered[index+1:]
yield buffered[:index + 1]
buffered = buffered[index + 1:]
if len(buffered) > 0:
yield buffered

View File

@@ -0,0 +1,58 @@
import functools
from itertools import chain
import logging
import pprint
import six
def format_call(args, kwargs):
args = (repr(a) for a in args)
kwargs = ("{0!s}={1!r}".format(*item) for item in six.iteritems(kwargs))
return "({0})".format(", ".join(chain(args, kwargs)))
def format_return(result, max_lines):
if isinstance(result, (list, tuple, set)):
return "({0} with {1} items)".format(type(result).__name__, len(result))
if result:
lines = pprint.pformat(result).split('\n')
extra = '\n...' if len(lines) > max_lines else ''
return '\n'.join(lines[:max_lines]) + extra
return result
class VerboseProxy(object):
"""Proxy all function calls to another class and log method name, arguments
and return values for each call.
"""
def __init__(self, obj_name, obj, log_name=None, max_lines=10):
self.obj_name = obj_name
self.obj = obj
self.max_lines = max_lines
self.log = logging.getLogger(log_name or __name__)
def __getattr__(self, name):
attr = getattr(self.obj, name)
if not six.callable(attr):
return attr
return functools.partial(self.proxy_callable, name)
def proxy_callable(self, call_name, *args, **kwargs):
self.log.info("%s %s <- %s",
self.obj_name,
call_name,
format_call(args, kwargs))
result = getattr(self.obj, call_name)(*args, **kwargs)
self.log.info("%s %s -> %s",
self.obj_name,
call_name,
format_return(result, self.max_lines))
return result

View File

@@ -1,6 +1,9 @@
from __future__ import unicode_literals
from __future__ import absolute_import
import six
class Container(object):
"""
Represents a Docker container, constructed from the output of
@@ -17,12 +20,10 @@ class Container(object):
Construct a container object from the output of GET /containers/json.
"""
new_dictionary = {
'ID': dictionary['Id'],
'Id': dictionary['Id'],
'Image': dictionary['Image'],
'Name': '/' + get_container_name(dictionary),
}
for name in dictionary.get('Names', []):
if len(name.split('/')) == 2:
new_dictionary['Name'] = name
return cls(client, new_dictionary, **kwargs)
@classmethod
@@ -36,7 +37,7 @@ class Container(object):
@property
def id(self):
return self.dictionary['ID']
return self.dictionary['Id']
@property
def image(self):
@@ -62,50 +63,58 @@ class Container(object):
return None
@property
def human_readable_ports(self):
def ports(self):
self.inspect_if_not_inspected()
if not self.dictionary['NetworkSettings']['Ports']:
return ''
ports = []
for private, public in list(self.dictionary['NetworkSettings']['Ports'].items()):
if public:
ports.append('%s->%s' % (public[0]['HostPort'], private))
else:
ports.append(private)
return ', '.join(ports)
return self.get('NetworkSettings.Ports') or {}
@property
def human_readable_ports(self):
def format_port(private, public):
if not public:
return private
return '{HostIp}:{HostPort}->{private}'.format(
private=private, **public[0])
return ', '.join(format_port(*item)
for item in sorted(six.iteritems(self.ports)))
@property
def human_readable_state(self):
self.inspect_if_not_inspected()
if self.dictionary['State']['Running']:
if self.dictionary['State'].get('Ghost'):
return 'Ghost'
else:
return 'Up'
if self.is_running:
return 'Ghost' if self.get('State.Ghost') else 'Up'
else:
return 'Exit %s' % self.dictionary['State']['ExitCode']
return 'Exit %s' % self.get('State.ExitCode')
@property
def human_readable_command(self):
self.inspect_if_not_inspected()
if self.dictionary['Config']['Cmd']:
return ' '.join(self.dictionary['Config']['Cmd'])
else:
return ''
entrypoint = self.get('Config.Entrypoint') or []
cmd = self.get('Config.Cmd') or []
return ' '.join(entrypoint + cmd)
@property
def environment(self):
self.inspect_if_not_inspected()
out = {}
for var in self.dictionary.get('Config', {}).get('Env', []):
k, v = var.split('=', 1)
out[k] = v
return out
return dict(var.split("=", 1) for var in self.get('Config.Env') or [])
@property
def is_running(self):
return self.get('State.Running')
def get(self, key):
"""Return a value from the container or None if the value is not set.
:param key: a string using dotted notation for nested dictionary
lookups
"""
self.inspect_if_not_inspected()
return self.dictionary['State']['Running']
def get_value(dictionary, key):
return (dictionary or {}).get(key)
return reduce(get_value, key.split('.'), self.dictionary)
def get_local_port(self, port, protocol='tcp'):
port = self.ports.get("%s/%s" % (port, protocol))
return "{HostIp}:{HostPort}".format(**port[0]) if port else None
def start(self, **options):
return self.client.start(self.id, **options)
@@ -113,8 +122,11 @@ class Container(object):
def stop(self, **options):
return self.client.stop(self.id, **options)
def kill(self):
return self.client.kill(self.id)
def kill(self, **options):
return self.client.kill(self.id, **options)
def restart(self):
return self.client.restart(self.id)
def remove(self, **options):
return self.client.remove_container(self.id, **options)
@@ -131,6 +143,7 @@ class Container(object):
def inspect(self):
self.dictionary = self.client.inspect_container(self.id)
self.has_been_inspected = True
return self.dictionary
def links(self):
@@ -155,3 +168,14 @@ class Container(object):
if type(self) != type(other):
return False
return self.id == other.id
def get_container_name(container):
if not container.get('Name') and not container.get('Names'):
return None
# inspect
if 'Name' in container:
return container['Name']
# ps
shortest_name = min(container['Names'], key=lambda n: len(n.split('/')))
return shortest_name.split('/')[-1]

View File

@@ -19,7 +19,9 @@ def stream_output(output, stream):
all_events.append(event)
if 'progress' in event or 'progressDetail' in event:
image_id = event['id']
image_id = event.get('id')
if not image_id:
continue
if image_id in lines:
diff = len(lines) - lines[image_id]

242
compose/project.py Normal file
View File

@@ -0,0 +1,242 @@
from __future__ import unicode_literals
from __future__ import absolute_import
import logging
from .service import Service
from .container import Container
from docker.errors import APIError
log = logging.getLogger(__name__)
def sort_service_dicts(services):
# Topological sort (Cormen/Tarjan algorithm).
unmarked = services[:]
temporary_marked = set()
sorted_services = []
def get_service_names(links):
return [link.split(':')[0] for link in links]
def visit(n):
if n['name'] in temporary_marked:
if n['name'] in get_service_names(n.get('links', [])):
raise DependencyError('A service can not link to itself: %s' % n['name'])
if n['name'] in n.get('volumes_from', []):
raise DependencyError('A service can not mount itself as volume: %s' % n['name'])
else:
raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
if n in unmarked:
temporary_marked.add(n['name'])
dependents = [m for m in services if (n['name'] in get_service_names(m.get('links', []))) or (n['name'] in m.get('volumes_from', []))]
for m in dependents:
visit(m)
temporary_marked.remove(n['name'])
unmarked.remove(n)
sorted_services.insert(0, n)
while unmarked:
visit(unmarked[-1])
return sorted_services
class Project(object):
"""
A collection of services.
"""
def __init__(self, name, services, client):
self.name = name
self.services = services
self.client = client
@classmethod
def from_dicts(cls, name, service_dicts, client):
"""
Construct a ServiceCollection from a list of dicts representing services.
"""
project = cls(name, [], client)
for service_dict in sort_service_dicts(service_dicts):
links = project.get_links(service_dict)
volumes_from = project.get_volumes_from(service_dict)
project.services.append(Service(client=client, project=name, links=links, volumes_from=volumes_from, **service_dict))
return project
@classmethod
def from_config(cls, name, config, client):
dicts = []
for service_name, service in list(config.items()):
if not isinstance(service, dict):
raise ConfigurationError('Service "%s" doesn\'t have any configuration options. All top level keys in your docker-compose.yml must map to a dictionary of configuration options.' % service_name)
service['name'] = service_name
dicts.append(service)
return cls.from_dicts(name, dicts, client)
def get_service(self, name):
"""
Retrieve a service by name. Raises NoSuchService
if the named service does not exist.
"""
for service in self.services:
if service.name == name:
return service
raise NoSuchService(name)
def get_services(self, service_names=None, include_links=False):
"""
Returns a list of this project's services filtered
by the provided list of names, or all services if service_names is None
or [].
If include_links is specified, returns a list including the links for
service_names, in order of dependency.
Preserves the original order of self.services where possible,
reordering as needed to resolve links.
Raises NoSuchService if any of the named services do not exist.
"""
if service_names is None or len(service_names) == 0:
return self.get_services(
service_names=[s.name for s in self.services],
include_links=include_links
)
else:
unsorted = [self.get_service(name) for name in service_names]
services = [s for s in self.services if s in unsorted]
if include_links:
services = reduce(self._inject_links, services, [])
uniques = []
[uniques.append(s) for s in services if s not in uniques]
return uniques
def get_links(self, service_dict):
links = []
if 'links' in service_dict:
for link in service_dict.get('links', []):
if ':' in link:
service_name, link_name = link.split(':', 1)
else:
service_name, link_name = link, None
try:
links.append((self.get_service(service_name), link_name))
except NoSuchService:
raise ConfigurationError('Service "%s" has a link to service "%s" which does not exist.' % (service_dict['name'], service_name))
del service_dict['links']
return links
def get_volumes_from(self, service_dict):
volumes_from = []
if 'volumes_from' in service_dict:
for volume_name in service_dict.get('volumes_from', []):
try:
service = self.get_service(volume_name)
volumes_from.append(service)
except NoSuchService:
try:
container = Container.from_id(self.client, volume_name)
volumes_from.append(container)
except APIError:
raise ConfigurationError('Service "%s" mounts volumes from "%s", which is not the name of a service or container.' % (service_dict['name'], volume_name))
del service_dict['volumes_from']
return volumes_from
def start(self, service_names=None, **options):
for service in self.get_services(service_names):
service.start(**options)
def stop(self, service_names=None, **options):
for service in reversed(self.get_services(service_names)):
service.stop(**options)
def kill(self, service_names=None, **options):
for service in reversed(self.get_services(service_names)):
service.kill(**options)
def restart(self, service_names=None, **options):
for service in self.get_services(service_names):
service.restart(**options)
def build(self, service_names=None, no_cache=False):
for service in self.get_services(service_names):
if service.can_be_built():
service.build(no_cache)
else:
log.info('%s uses an image, skipping' % service.name)
def up(self,
service_names=None,
start_links=True,
recreate=True,
insecure_registry=False,
detach=False,
do_build=True):
running_containers = []
for service in self.get_services(service_names, include_links=start_links):
if recreate:
for (_, container) in service.recreate_containers(
insecure_registry=insecure_registry,
detach=detach,
do_build=do_build):
running_containers.append(container)
else:
for container in service.start_or_create_containers(
insecure_registry=insecure_registry,
detach=detach,
do_build=do_build):
running_containers.append(container)
return running_containers
def pull(self, service_names=None, insecure_registry=False):
for service in self.get_services(service_names, include_links=True):
service.pull(insecure_registry=insecure_registry)
def remove_stopped(self, service_names=None, **options):
for service in self.get_services(service_names):
service.remove_stopped(**options)
def containers(self, service_names=None, stopped=False, one_off=False):
return [Container.from_ps(self.client, container)
for container in self.client.containers(all=stopped)
for service in self.get_services(service_names)
if service.has_container(container, one_off=one_off)]
def _inject_links(self, acc, service):
linked_names = service.get_linked_names()
if len(linked_names) > 0:
linked_services = self.get_services(
service_names=linked_names,
include_links=True
)
else:
linked_services = []
linked_services.append(service)
return acc + linked_services
class NoSuchService(Exception):
def __init__(self, name):
self.name = name
self.msg = "No such service: %s" % self.name
def __str__(self):
return self.msg
class ConfigurationError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class DependencyError(ConfigurationError):
pass

673
compose/service.py Normal file
View File

@@ -0,0 +1,673 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from collections import namedtuple
import logging
import re
import os
from operator import attrgetter
import sys
from docker.errors import APIError
from .container import Container, get_container_name
from .progress_stream import stream_output, StreamOutputError
log = logging.getLogger(__name__)
DOCKER_CONFIG_KEYS = [
'cap_add',
'cap_drop',
'cpu_shares',
'command',
'detach',
'dns',
'dns_search',
'domainname',
'entrypoint',
'env_file',
'environment',
'hostname',
'image',
'mem_limit',
'net',
'ports',
'privileged',
'restart',
'stdin_open',
'tty',
'user',
'volumes',
'volumes_from',
'working_dir',
]
DOCKER_CONFIG_HINTS = {
'cpu_share' : 'cpu_shares',
'link' : 'links',
'port' : 'ports',
'privilege' : 'privileged',
'priviliged': 'privileged',
'privilige' : 'privileged',
'volume' : 'volumes',
'workdir' : 'working_dir',
}
DOCKER_START_KEYS = [
'cap_add',
'cap_drop',
'dns',
'dns_search',
'env_file',
'net',
'privileged',
'restart',
]
VALID_NAME_CHARS = '[a-zA-Z0-9]'
class BuildError(Exception):
def __init__(self, service, reason):
self.service = service
self.reason = reason
class CannotBeScaledError(Exception):
pass
class ConfigError(ValueError):
pass
VolumeSpec = namedtuple('VolumeSpec', 'external internal mode')
ServiceName = namedtuple('ServiceName', 'project service number')
class Service(object):
def __init__(self, name, client=None, project='default', links=None, external_links=None, volumes_from=None, **options):
if not re.match('^%s+$' % VALID_NAME_CHARS, name):
raise ConfigError('Invalid service name "%s" - only %s are allowed' % (name, VALID_NAME_CHARS))
if not re.match('^%s+$' % VALID_NAME_CHARS, project):
raise ConfigError('Invalid project name "%s" - only %s are allowed' % (project, VALID_NAME_CHARS))
if 'image' in options and 'build' in options:
raise ConfigError('Service %s has both an image and build path specified. A service can either be built to image or use an existing image, not both.' % name)
for filename in get_env_files(options):
if not os.path.exists(filename):
raise ConfigError("Couldn't find env file for service %s: %s" % (name, filename))
supported_options = DOCKER_CONFIG_KEYS + ['build', 'expose',
'external_links']
for k in options:
if k not in supported_options:
msg = "Unsupported config option for %s service: '%s'" % (name, k)
if k in DOCKER_CONFIG_HINTS:
msg += " (did you mean '%s'?)" % DOCKER_CONFIG_HINTS[k]
raise ConfigError(msg)
self.name = name
self.client = client
self.project = project
self.links = links or []
self.external_links = external_links or []
self.volumes_from = volumes_from or []
self.options = options
def containers(self, stopped=False, one_off=False):
return [Container.from_ps(self.client, container)
for container in self.client.containers(all=stopped)
if self.has_container(container, one_off=one_off)]
def has_container(self, container, one_off=False):
"""Return True if `container` was created to fulfill this service."""
name = get_container_name(container)
if not name or not is_valid_name(name, one_off):
return False
project, name, _number = parse_name(name)
return project == self.project and name == self.name
def get_container(self, number=1):
"""Return a :class:`compose.container.Container` for this service. The
container must be active, and match `number`.
"""
for container in self.client.containers():
if not self.has_container(container):
continue
_, _, container_number = parse_name(get_container_name(container))
if container_number == number:
return Container.from_ps(self.client, container)
raise ValueError("No container found for %s_%s" % (self.name, number))
def start(self, **options):
for c in self.containers(stopped=True):
self.start_container_if_stopped(c, **options)
def stop(self, **options):
for c in self.containers():
log.info("Stopping %s..." % c.name)
c.stop(**options)
def kill(self, **options):
for c in self.containers():
log.info("Killing %s..." % c.name)
c.kill(**options)
def restart(self, **options):
for c in self.containers():
log.info("Restarting %s..." % c.name)
c.restart(**options)
def scale(self, desired_num):
"""
Adjusts the number of containers to the specified number and ensures
they are running.
- creates containers until there are at least `desired_num`
- stops containers until there are at most `desired_num` running
- starts containers until there are at least `desired_num` running
- removes all stopped containers
"""
if not self.can_be_scaled():
raise CannotBeScaledError()
# Create enough containers
containers = self.containers(stopped=True)
while len(containers) < desired_num:
log.info("Creating %s..." % self._next_container_name(containers))
containers.append(self.create_container(detach=True))
running_containers = []
stopped_containers = []
for c in containers:
if c.is_running:
running_containers.append(c)
else:
stopped_containers.append(c)
running_containers.sort(key=lambda c: c.number)
stopped_containers.sort(key=lambda c: c.number)
# Stop containers
while len(running_containers) > desired_num:
c = running_containers.pop()
log.info("Stopping %s..." % c.name)
c.stop(timeout=1)
stopped_containers.append(c)
# Start containers
while len(running_containers) < desired_num:
c = stopped_containers.pop(0)
log.info("Starting %s..." % c.name)
self.start_container(c)
running_containers.append(c)
self.remove_stopped()
def remove_stopped(self, **options):
for c in self.containers(stopped=True):
if not c.is_running:
log.info("Removing %s..." % c.name)
c.remove(**options)
def create_container(self,
one_off=False,
insecure_registry=False,
do_build=True,
**override_options):
"""
Create a container for this service. If the image doesn't exist, attempt to pull
it.
"""
container_options = self._get_container_create_options(
override_options,
one_off=one_off)
if (do_build and
self.can_be_built() and
not self.client.images(name=self.full_name)):
self.build()
try:
return Container.create(self.client, **container_options)
except APIError as e:
if e.response.status_code == 404 and e.explanation and 'No such image' in str(e.explanation):
log.info('Pulling image %s...' % container_options['image'])
output = self.client.pull(
container_options['image'],
stream=True,
insecure_registry=insecure_registry
)
stream_output(output, sys.stdout)
return Container.create(self.client, **container_options)
raise
def recreate_containers(self, insecure_registry=False, do_build=True, **override_options):
"""
If a container for this service doesn't exist, create and start one. If there are
any, stop them, create+start new ones, and remove the old containers.
"""
containers = self.containers(stopped=True)
if not containers:
log.info("Creating %s..." % self._next_container_name(containers))
container = self.create_container(
insecure_registry=insecure_registry,
do_build=do_build,
**override_options)
self.start_container(container)
return [(None, container)]
else:
tuples = []
for c in containers:
log.info("Recreating %s..." % c.name)
tuples.append(self.recreate_container(c, insecure_registry=insecure_registry, **override_options))
return tuples
def recreate_container(self, container, **override_options):
"""Recreate a container. An intermediate container is created so that
the new container has the same name, while still supporting
`volumes-from` the original container.
"""
try:
container.stop()
except APIError as e:
if (e.response.status_code == 500
and e.explanation
and 'no such process' in str(e.explanation)):
pass
else:
raise
intermediate_container = Container.create(
self.client,
image=container.image,
entrypoint=['/bin/echo'],
command=[],
detach=True,
)
intermediate_container.start(volumes_from=container.id)
intermediate_container.wait()
container.remove()
options = dict(override_options)
new_container = self.create_container(do_build=False, **options)
self.start_container(new_container, intermediate_container=intermediate_container)
intermediate_container.remove()
return (intermediate_container, new_container)
def start_container_if_stopped(self, container, **options):
if container.is_running:
return container
else:
log.info("Starting %s..." % container.name)
return self.start_container(container, **options)
def start_container(self, container, intermediate_container=None, **override_options):
options = dict(self.options, **override_options)
port_bindings = build_port_bindings(options.get('ports') or [])
volume_bindings = dict(
build_volume_binding(parse_volume_spec(volume))
for volume in options.get('volumes') or []
if ':' in volume)
privileged = options.get('privileged', False)
net = options.get('net', 'bridge')
dns = options.get('dns', None)
dns_search = options.get('dns_search', None)
cap_add = options.get('cap_add', None)
cap_drop = options.get('cap_drop', None)
restart = parse_restart_spec(options.get('restart', None))
container.start(
links=self._get_links(link_to_self=options.get('one_off', False)),
port_bindings=port_bindings,
binds=volume_bindings,
volumes_from=self._get_volumes_from(intermediate_container),
privileged=privileged,
network_mode=net,
dns=dns,
dns_search=dns_search,
restart_policy=restart,
cap_add=cap_add,
cap_drop=cap_drop,
)
return container
def start_or_create_containers(
self,
insecure_registry=False,
detach=False,
do_build=True):
containers = self.containers(stopped=True)
if not containers:
log.info("Creating %s..." % self._next_container_name(containers))
new_container = self.create_container(
insecure_registry=insecure_registry,
detach=detach,
do_build=do_build,
)
return [self.start_container(new_container)]
else:
return [self.start_container_if_stopped(c) for c in containers]
def get_linked_names(self):
return [s.name for (s, _) in self.links]
def _next_container_name(self, all_containers, one_off=False):
bits = [self.project, self.name]
if one_off:
bits.append('run')
return '_'.join(bits + [str(self._next_container_number(all_containers))])
def _next_container_number(self, all_containers):
numbers = [parse_name(c.name).number for c in all_containers]
return 1 if not numbers else max(numbers) + 1
def _get_links(self, link_to_self):
links = []
for service, link_name in self.links:
for container in service.containers():
links.append((container.name, link_name or service.name))
links.append((container.name, container.name))
links.append((container.name, container.name_without_project))
if link_to_self:
for container in self.containers():
links.append((container.name, self.name))
links.append((container.name, container.name))
links.append((container.name, container.name_without_project))
for external_link in self.external_links:
if ':' not in external_link:
link_name = external_link
else:
external_link, link_name = external_link.split(':')
links.append((external_link, link_name))
return links
def _get_volumes_from(self, intermediate_container=None):
volumes_from = []
for volume_source in self.volumes_from:
if isinstance(volume_source, Service):
containers = volume_source.containers(stopped=True)
if not containers:
volumes_from.append(volume_source.create_container().id)
else:
volumes_from.extend(map(attrgetter('id'), containers))
elif isinstance(volume_source, Container):
volumes_from.append(volume_source.id)
if intermediate_container:
volumes_from.append(intermediate_container.id)
return volumes_from
def _get_container_create_options(self, override_options, one_off=False):
container_options = dict(
(k, self.options[k])
for k in DOCKER_CONFIG_KEYS if k in self.options)
container_options.update(override_options)
container_options['name'] = self._next_container_name(
self.containers(stopped=True, one_off=one_off),
one_off)
# If a qualified hostname was given, split it into an
# unqualified hostname and a domainname unless domainname
# was also given explicitly. This matches the behavior of
# the official Docker CLI in that scenario.
if ('hostname' in container_options
and 'domainname' not in container_options
and '.' in container_options['hostname']):
parts = container_options['hostname'].partition('.')
container_options['hostname'] = parts[0]
container_options['domainname'] = parts[2]
if 'ports' in container_options or 'expose' in self.options:
ports = []
all_ports = container_options.get('ports', []) + self.options.get('expose', [])
for port in all_ports:
port = str(port)
if ':' in port:
port = port.split(':')[-1]
if '/' in port:
port = tuple(port.split('/'))
ports.append(port)
container_options['ports'] = ports
if 'volumes' in container_options:
container_options['volumes'] = dict(
(parse_volume_spec(v).internal, {})
for v in container_options['volumes'])
container_options['environment'] = merge_environment(container_options)
if self.can_be_built():
container_options['image'] = self.full_name
else:
container_options['image'] = self._get_image_name(container_options['image'])
# Delete options which are only used when starting
for key in DOCKER_START_KEYS:
container_options.pop(key, None)
return container_options
def _get_image_name(self, image):
repo, tag = parse_repository_tag(image)
if tag == "":
tag = "latest"
return '%s:%s' % (repo, tag)
def build(self, no_cache=False):
log.info('Building %s...' % self.name)
build_output = self.client.build(
self.options['build'],
tag=self.full_name,
stream=True,
rm=True,
nocache=no_cache,
)
try:
all_events = stream_output(build_output, sys.stdout)
except StreamOutputError, e:
raise BuildError(self, unicode(e))
image_id = None
for event in all_events:
if 'stream' in event:
match = re.search(r'Successfully built ([0-9a-f]+)', event.get('stream', ''))
if match:
image_id = match.group(1)
if image_id is None:
raise BuildError(self, event if all_events else 'Unknown')
return image_id
def can_be_built(self):
return 'build' in self.options
@property
def full_name(self):
"""
The tag to give to images built for this service.
"""
return '%s_%s' % (self.project, self.name)
def can_be_scaled(self):
for port in self.options.get('ports', []):
if ':' in str(port):
return False
return True
def pull(self, insecure_registry=False):
if 'image' in self.options:
image_name = self._get_image_name(self.options['image'])
log.info('Pulling %s (%s)...' % (self.name, image_name))
self.client.pull(
image_name,
insecure_registry=insecure_registry
)
NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(run_)?(\d+)$')
def is_valid_name(name, one_off=False):
match = NAME_RE.match(name)
if match is None:
return False
if one_off:
return match.group(3) == 'run_'
else:
return match.group(3) is None
def parse_name(name):
match = NAME_RE.match(name)
(project, service_name, _, suffix) = match.groups()
return ServiceName(project, service_name, int(suffix))
def parse_restart_spec(restart_config):
if not restart_config:
return None
parts = restart_config.split(':')
if len(parts) > 2:
raise ConfigError("Restart %s has incorrect format, should be "
"mode[:max_retry]" % restart_config)
if len(parts) == 2:
name, max_retry_count = parts
else:
name, = parts
max_retry_count = 0
return {'Name': name, 'MaximumRetryCount': int(max_retry_count)}
def parse_volume_spec(volume_config):
parts = volume_config.split(':')
if len(parts) > 3:
raise ConfigError("Volume %s has incorrect format, should be "
"external:internal[:mode]" % volume_config)
if len(parts) == 1:
return VolumeSpec(None, parts[0], 'rw')
if len(parts) == 2:
parts.append('rw')
external, internal, mode = parts
if mode not in ('rw', 'ro'):
raise ConfigError("Volume %s has invalid mode (%s), should be "
"one of: rw, ro." % (volume_config, mode))
return VolumeSpec(external, internal, mode)
def parse_repository_tag(s):
if ":" not in s:
return s, ""
repo, tag = s.rsplit(":", 1)
if "/" in tag:
return s, ""
return repo, tag
def build_volume_binding(volume_spec):
internal = {'bind': volume_spec.internal, 'ro': volume_spec.mode == 'ro'}
external = os.path.expanduser(volume_spec.external)
return os.path.abspath(os.path.expandvars(external)), internal
def build_port_bindings(ports):
port_bindings = {}
for port in ports:
internal_port, external = split_port(port)
if internal_port in port_bindings:
port_bindings[internal_port].append(external)
else:
port_bindings[internal_port] = [external]
return port_bindings
def split_port(port):
parts = str(port).split(':')
if not 1 <= len(parts) <= 3:
raise ConfigError('Invalid port "%s", should be '
'[[remote_ip:]remote_port:]port[/protocol]' % port)
if len(parts) == 1:
internal_port, = parts
return internal_port, None
if len(parts) == 2:
external_port, internal_port = parts
return internal_port, external_port
external_ip, external_port, internal_port = parts
return internal_port, (external_ip, external_port or None)
def get_env_files(options):
env_files = options.get('env_file', [])
if not isinstance(env_files, list):
env_files = [env_files]
return env_files
def merge_environment(options):
env = {}
for f in get_env_files(options):
env.update(env_vars_from_file(f))
if 'environment' in options:
if isinstance(options['environment'], list):
env.update(dict(split_env(e) for e in options['environment']))
else:
env.update(options['environment'])
return dict(resolve_env(k, v) for k, v in env.iteritems())
def split_env(env):
if '=' in env:
return env.split('=', 1)
else:
return env, None
def resolve_env(key, val):
if val is not None:
return key, val
elif key in os.environ:
return key, os.environ[key]
else:
return key, ''
def env_vars_from_file(filename):
"""
Read in a line delimited file of environment variables.
"""
env = {}
for line in open(filename, 'r'):
line = line.strip()
if line and not line.startswith('#'):
k, v = split_env(line)
env[k] = v
return env

View File

@@ -0,0 +1,326 @@
#!bash
#
# bash completion for docker-compose
#
# This work is based on the completion for the docker command.
#
# This script provides completion of:
# - commands and their options
# - service names
# - filepaths
#
# To enable the completions either:
# - place this file in /etc/bash_completion.d
# or
# - copy this file to e.g. ~/.docker-compose-completion.sh and add the line
# below to your .bashrc after bash completion features are loaded
# . ~/.docker-compose-completion.sh
# For compatibility reasons, Compose and therefore its completion supports several
# stack compositon files as listed here, in descending priority.
# Support for these filenames might be dropped in some future version.
__docker-compose_compose_file() {
local file
for file in docker-compose.y{,a}ml fig.y{,a}ml ; do
[ -e $file ] && {
echo $file
return
}
done
echo docker-compose.yml
}
# Extracts all service names from the compose file.
___docker-compose_all_services_in_compose_file() {
awk -F: '/^[a-zA-Z0-9]/{print $1}' "${compose_file:-$(__docker-compose_compose_file)}" 2>/dev/null
}
# All services, even those without an existing container
__docker-compose_services_all() {
COMPREPLY=( $(compgen -W "$(___docker-compose_all_services_in_compose_file)" -- "$cur") )
}
# All services that have an entry with the given key in their compose_file section
___docker-compose_services_with_key() {
# flatten sections to one line, then filter lines containing the key and return section name.
awk '/^[a-zA-Z0-9]/{printf "\n"};{printf $0;next;}' "${compose_file:-$(__docker-compose_compose_file)}" | awk -F: -v key=": +$1:" '$0 ~ key {print $1}'
}
# All services that are defined by a Dockerfile reference
__docker-compose_services_from_build() {
COMPREPLY=( $(compgen -W "$(___docker-compose_services_with_key build)" -- "$cur") )
}
# All services that are defined by an image
__docker-compose_services_from_image() {
COMPREPLY=( $(compgen -W "$(___docker-compose_services_with_key image)" -- "$cur") )
}
# The services for which containers have been created, optionally filtered
# by a boolean expression passed in as argument.
__docker-compose_services_with() {
local containers names
containers="$(docker-compose 2>/dev/null ${compose_file:+-f $compose_file} ${compose_project:+-p $compose_project} ps -q)"
names=( $(docker 2>/dev/null inspect --format "{{if ${1:-true}}} {{ .Name }} {{end}}" $containers) )
names=( ${names[@]%_*} ) # strip trailing numbers
names=( ${names[@]#*_} ) # strip project name
COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") )
}
# The services for which at least one running container exists
__docker-compose_services_running() {
__docker-compose_services_with '.State.Running'
}
# The services for which at least one stopped container exists
__docker-compose_services_stopped() {
__docker-compose_services_with 'not .State.Running'
}
_docker-compose_build() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--no-cache" -- "$cur" ) )
;;
*)
__docker-compose_services_from_build
;;
esac
}
_docker-compose_docker-compose() {
case "$prev" in
--file|-f)
_filedir
return
;;
--project-name|-p)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help -h --verbose --version --file -f --project-name -p" -- "$cur" ) )
;;
*)
COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) )
;;
esac
}
_docker-compose_help() {
COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) )
}
_docker-compose_kill() {
case "$prev" in
-s)
COMPREPLY=( $( compgen -W "SIGHUP SIGINT SIGKILL SIGUSR1 SIGUSR2" -- "$(echo $cur | tr '[:lower:]' '[:upper:]')" ) )
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-s" -- "$cur" ) )
;;
*)
__docker-compose_services_running
;;
esac
}
_docker-compose_logs() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--no-color" -- "$cur" ) )
;;
*)
__docker-compose_services_all
;;
esac
}
_docker-compose_port() {
case "$prev" in
--protocol)
COMPREPLY=( $( compgen -W "tcp udp" -- "$cur" ) )
return;
;;
--index)
return;
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--protocol --index" -- "$cur" ) )
;;
*)
__docker-compose_services_all
;;
esac
}
_docker-compose_ps() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-q" -- "$cur" ) )
;;
*)
__docker-compose_services_all
;;
esac
}
_docker-compose_pull() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--allow-insecure-ssl" -- "$cur" ) )
;;
*)
__docker-compose_services_from_image
;;
esac
}
_docker-compose_restart() {
__docker-compose_services_running
}
_docker-compose_rm() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--force -v" -- "$cur" ) )
;;
*)
__docker-compose_services_stopped
;;
esac
}
_docker-compose_run() {
case "$prev" in
-e)
COMPREPLY=( $( compgen -e -- "$cur" ) )
compopt -o nospace
return
;;
--entrypoint)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--allow-insecure-ssl -d --entrypoint -e --no-deps --rm --service-ports -T" -- "$cur" ) )
;;
*)
__docker-compose_services_all
;;
esac
}
_docker-compose_scale() {
case "$prev" in
=)
COMPREPLY=("$cur")
;;
*)
COMPREPLY=( $(compgen -S "=" -W "$(___docker-compose_all_services_in_compose_file)" -- "$cur") )
compopt -o nospace
;;
esac
}
_docker-compose_start() {
__docker-compose_services_stopped
}
_docker-compose_stop() {
__docker-compose_services_running
}
_docker-compose_up() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--allow-insecure-ssl -d --no-build --no-color --no-deps --no-recreate" -- "$cur" ) )
;;
*)
__docker-compose_services_all
;;
esac
}
_docker-compose() {
local commands=(
build
help
kill
logs
port
ps
pull
restart
rm
run
scale
start
stop
up
)
COMPREPLY=()
local cur prev words cword
_get_comp_words_by_ref -n : cur prev words cword
# search subcommand and invoke its handler.
# special treatment of some top-level options
local command='docker-compose'
local counter=1
local compose_file compose_project
while [ $counter -lt $cword ]; do
case "${words[$counter]}" in
-f|--file)
(( counter++ ))
compose_file="${words[$counter]}"
;;
-p|--project-name)
(( counter++ ))
compose_project="${words[$counter]}"
;;
-*)
;;
*)
command="${words[$counter]}"
break
;;
esac
(( counter++ ))
done
local completions_func=_docker-compose_${command}
declare -F $completions_func >/dev/null && $completions_func
return 0
}
complete -F _docker-compose docker-compose

View File

@@ -1 +0,0 @@
/_site

View File

@@ -1,10 +1,15 @@
FROM stackbrew/ubuntu:13.10
RUN apt-get -qq update && apt-get install -y ruby1.8 bundler python
RUN locale-gen en_US.UTF-8
ADD Gemfile /code/
ADD Gemfile.lock /code/
WORKDIR /code
RUN bundle install
ADD . /code
EXPOSE 4000
CMD bundle exec jekyll build
FROM docs/base:latest
MAINTAINER Sven Dowideit <SvenDowideit@docker.com> (@SvenDowideit)
# to get the git info for this repo
COPY . /src
# Reset the /docs dir so we can replace the theme meta with the new repo's git info
RUN git reset --hard
RUN grep "__version" /src/compose/__init__.py | sed "s/.*'\(.*\)'/\1/" > /docs/VERSION
COPY docs/* /docs/sources/compose/
COPY docs/mkdocs.yml /docs/mkdocs-compose.yml
# Then build everything together, ready for mkdocs
RUN /docs/build.sh

View File

@@ -1,3 +0,0 @@
source 'https://rubygems.org'
gem 'github-pages'

View File

@@ -1,62 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
RedCloth (4.2.9)
blankslate (2.1.2.4)
classifier (1.3.3)
fast-stemmer (>= 1.0.0)
colorator (0.1)
commander (4.1.5)
highline (~> 1.6.11)
fast-stemmer (1.0.2)
ffi (1.9.3)
github-pages (12)
RedCloth (= 4.2.9)
jekyll (= 1.4.2)
kramdown (= 1.2.0)
liquid (= 2.5.4)
maruku (= 0.7.0)
rdiscount (= 2.1.7)
redcarpet (= 2.3.0)
highline (1.6.20)
jekyll (1.4.2)
classifier (~> 1.3)
colorator (~> 0.1)
commander (~> 4.1.3)
liquid (~> 2.5.2)
listen (~> 1.3)
maruku (~> 0.7.0)
pygments.rb (~> 0.5.0)
redcarpet (~> 2.3.0)
safe_yaml (~> 0.9.7)
toml (~> 0.1.0)
kramdown (1.2.0)
liquid (2.5.4)
listen (1.3.1)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
rb-kqueue (>= 0.2)
maruku (0.7.0)
parslet (1.5.0)
blankslate (~> 2.0)
posix-spawn (0.3.8)
pygments.rb (0.5.4)
posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.1.0)
rb-fsevent (0.9.4)
rb-inotify (0.9.3)
ffi (>= 0.5.0)
rb-kqueue (0.2.0)
ffi (>= 0.5.0)
rdiscount (2.1.7)
redcarpet (2.3.0)
safe_yaml (0.9.7)
toml (0.1.0)
parslet (~> 1.5.0)
yajl-ruby (1.1.0)
PLATFORMS
ruby
DEPENDENCIES
github-pages

View File

@@ -1,3 +0,0 @@
markdown: redcarpet
encoding: utf-8

View File

@@ -1,71 +0,0 @@
<!DOCTYPE html>
<html lang="en-gb">
<head>
<meta charset="utf-8">
<title>{{ page.title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href='http://fonts.googleapis.com/css?family=Lilita+One|Lato:300,400,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/fig.css?{{ site.time | date:'%Y%m%d%U%H%N%S' }}">
</head>
<body>
<div class="container">
<div class="logo mobile-logo">
<a href="index.html">
<img src="img/logo.png">
Fig
</a>
</div>
<div class="content">{{ content }}</div>
<div class="sidebar">
<h1 class="logo">
<a href="index.html">
<img src="img/logo.png">
Fig
</a>
</h1>
<ul class="nav">
<li><a href="index.html">Home</a></li>
<li><a href="install.html">Install</a></li>
<li><a href="rails.html">Get started with Rails</a></li>
<li><a href="django.html">Get started with Django</a></li>
<li><a href="wordpress.html">Get started with Wordpress</a></li>
</ul>
<ul class="nav">
<li>Reference:</li>
<ul>
<li><a href="yml.html">fig.yml</a></li>
<li><a href="cli.html">Commands</a></li>
<li><a href="env.html">Environment variables</a></li>
</ul>
</ul>
<ul class="nav">
<li><a href="https://github.com/orchardup/fig">Fig on GitHub</a></li>
<li><a href="http://webchat.freenode.net/?channels=%23orchardup&uio=d4">#orchardup on Freenode</a></li>
</ul>
<p>Fig is a project from <a href="https://www.orchardup.com">Orchard</a>, a Docker hosting service.</p>
<p><a href="https://twitter.com/orchardup">Follow us on Twitter</a> to keep up to date with Fig and other Docker news.</p>
<div class="badges">
<iframe src="http://ghbtns.com/github-btn.html?user=orchardup&amp;repo=fig&amp;type=watch&amp;count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe>
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://orchardup.github.io/fig/">Tweet</a>
</div>
</div>
</div>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-43996733-3', 'orchardup.github.io');
ga('send', 'pageview');
</script>
</body>
</html>

View File

@@ -1,81 +1,181 @@
---
layout: default
title: Fig CLI reference
---
CLI reference
=============
Most commands are run against one or more services. If the service is omitted, it will apply to all services.
Run `fig [COMMAND] --help` for full usage.
## build
Build or rebuild services.
Services are built once and then tagged as `project_service`, e.g. `figtest_db`. If you change a service's `Dockerfile` or the contents of its build directory, you can run `fig build` to rebuild it.
## help
Get help on a command.
## kill
Force stop service containers.
## logs
View output from services.
## ps
List containers.
## rm
Remove stopped service containers.
page_title: Compose CLI reference
page_description: Compose CLI reference
page_keywords: fig, composition, compose, docker, orchestration, cli, reference
## run
# CLI reference
Run a one-off command on a service.
Most Docker Compose commands are run against one or more services. If
the service is not specified, the command will apply to all services.
For example:
For full usage information, run `docker-compose [COMMAND] --help`.
$ fig run web python manage.py shell
## Commands
Note that this will not start any services that the command's service links to. So if, for example, your one-off command talks to your database, you will need to run `fig up -d db` first.
### build
One-off commands are started in new containers with the same config as a normal container for that service, so volumes, links, etc will all be created as expected. The only thing different to a normal container is the command will be overridden with the one specified and no ports will be created in case they collide.
Builds or rebuilds services.
Links are also created between one-off commands and the other containers for that service so you can do stuff like this:
Services are built once and then tagged as `project_service`, e.g.,
`composetest_db`. If you change a service's Dockerfile or the contents of its
build directory, run `docker-compose build` to rebuild it.
$ fig run db /bin/sh -c "psql -h \$DB_1_PORT_5432_TCP_ADDR -U docker"
### help
## scale
Displays help and usage instructions for a command.
Set number of containers to run for a service.
### kill
Numbers are specified in the form `service=num` as arguments.
For example:
Forces running containers to stop by sending a `SIGKILL` signal. Optionally the
signal can be passed, for example:
$ fig scale web=2 worker=3
$ docker-compose kill -s SIGINT
## start
### logs
Start existing containers for a service.
Displays log output from services.
## stop
### port
Stop running containers without removing them. They can be started again with `fig start`.
Prints the public port for a port binding
## up
### ps
Build, (re)create, start and attach to containers for a service.
Lists containers.
By default, `fig up` will aggregate the output of each container, and when it exits, all containers will be stopped. If you run `fig up -d`, it'll start the containers in the background and leave them running.
### pull
If there are existing containers for a service, `fig up` will stop and recreate them (preserving mounted volumes with [volumes-from]), so that changes in `fig.yml` are picked up.
Pulls service images.
### rm
Removes stopped service containers.
### run
Runs a one-off command on a service.
For example,
$ docker-compose run web python manage.py shell
will start the `web` service and then run `manage.py shell` in python.
Note that by default, linked services will also be started, unless they are
already running.
One-off commands are started in new containers with the same configuration as a
normal container for that service, so volumes, links, etc will all be created as
expected. When using `run`, there are two differences from bringing up a
container normally:
1. the command will be overridden with the one specified. So, if you run
`docker-compose run web bash`, the container's web command (which could default
to, e.g., `python app.py`) will be overridden to `bash`
2. by default no ports will be created in case they collide with already opened
ports.
Links are also created between one-off commands and the other containers which
are part of that service. So, for example, you could run:
$ docker-compose run db psql -h db -U docker
This would open up an interactive PostgreSQL shell for the linked `db` container
(which would get created or started as needed).
If you do not want linked containers to start when running the one-off command,
specify the `--no-deps` flag:
$ docker-compose run --no-deps web python manage.py shell
Similarly, if you do want the service's ports to be created and mapped to the
host, specify the `--service-ports` flag:
$ docker-compose run --service-ports web python manage.py shell
### scale
Sets the number of containers to run for a service.
Numbers are specified as arguments in the form `service=num`. For example:
$ docker-compose scale web=2 worker=3
### start
Starts existing containers for a service.
### stop
Stops running containers without removing them. They can be started again with
`docker-compose start`.
### up
Builds, (re)creates, starts, and attaches to containers for a service.
Linked services will be started, unless they are already running.
By default, `docker-compose up` will aggregate the output of each container and,
when it exits, all containers will be stopped. Running `docker-compose up -d`,
will start the containers in the background and leave them running.
By default, if there are existing containers for a service, `docker-compose up` will stop and recreate them (preserving mounted volumes with [volumes-from]), so that changes in `docker-compose.yml` are picked up. If you do not want containers stopped and recreated, use `docker-compose up --no-recreate`. This will still start any stopped containers, if needed.
[volumes-from]: http://docs.docker.io/en/latest/use/working_with_volumes/
## Options
### --verbose
Shows more output
### --version
Prints version and exits
### -f, --file FILE
Specifies an alternate Compose yaml file (default: `docker-compose.yml`)
### -p, --project-name NAME
Specifies an alternate project name (default: current directory name)
## Environment Variables
Several environment variables are available for you to configure Compose's behaviour.
Variables starting with `DOCKER_` are the same as those used to configure the
Docker command-line client. If you're using boot2docker, `$(boot2docker shellinit)`
will set them to their correct values.
### COMPOSE\_PROJECT\_NAME
Sets the project name, which is prepended to the name of every container started by Compose. Defaults to the `basename` of the current working directory.
### COMPOSE\_FILE
Sets the path to the `docker-compose.yml` to use. Defaults to `docker-compose.yml` in the current working directory.
### DOCKER\_HOST
Sets the URL of the docker daemon. As with the Docker client, defaults to `unix:///var/run/docker.sock`.
### DOCKER\_TLS\_VERIFY
When set to anything other than an empty string, enables TLS communication with
the daemon.
### DOCKER\_CERT\_PATH
Configures the path to the `ca.pem`, `cert.pem`, and `key.pem` files used for TLS verification. Defaults to `~/.docker`.
## Compose documentation
- [Installing Compose](install.md)
- [User guide](index.md)
- [Yaml file reference](yml.md)
- [Compose environment variables](env.md)
- [Compose command line completion](completion.md)

41
docs/completion.md Normal file
View File

@@ -0,0 +1,41 @@
---
layout: default
title: Command Completion
---
Command Completion
==================
Compose comes with [command completion](http://en.wikipedia.org/wiki/Command-line_completion)
for the bash shell.
Installing Command Completion
-----------------------------
Make sure bash completion is installed. If you use a current Linux in a non-minimal installation, bash completion should be available.
On a Mac, install with `brew install bash-completion`
Place the completion script in `/etc/bash_completion.d/` (`/usr/local/etc/bash_completion.d/` on a Mac), using e.g.
curl -L https://raw.githubusercontent.com/docker/compose/1.1.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
Completion will be available upon next login.
Available completions
---------------------
Depending on what you typed on the command line so far, it will complete
- available docker-compose commands
- options that are available for a particular command
- service names that make sense in a given context (e.g. services with running or stopped instances or services based on images vs. services based on Dockerfiles). For `docker-compose scale`, completed service names will automatically have "=" appended.
- arguments for selected options, e.g. `docker-compose kill -s` will complete some signals like SIGHUP and SIGUSR1.
Enjoy working with Compose faster and with less typos!
## Compose documentation
- [Installing Compose](install.md)
- [User guide](index.md)
- [Command line reference](cli.md)
- [Yaml file reference](yml.md)
- [Compose environment variables](env.md)

File diff suppressed because one or more lines are too long

View File

@@ -1,187 +0,0 @@
body {
padding-top: 20px;
padding-bottom: 60px;
font-family: 'Lato', sans-serif;
font-weight: 300;
font-size: 18px;
color: #362;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Lato', sans-serif;
font-weight: 400;
color: #25594D;
}
h2, h3, h4, h5, h6 {
margin-top: 1.5em;
}
p {
margin: 20px 0;
}
a, a:hover, a:visited {
color: #4D9900;
text-decoration: underline;
}
pre, code {
border: none;
background: #D5E1B4;
}
code, pre code {
color: #484F40;
}
pre {
border-bottom: 2px solid #bec9a1;
font-size: 14px;
}
code {
font-size: 0.84em;
}
pre code {
background: none;
}
img {
max-width: 100%;
}
.container {
margin-left: 0;
}
.logo {
font-family: 'Lilita One', sans-serif;
font-size: 64px;
margin: 20px 0 40px 0;
}
.logo a {
color: #a41211;
text-decoration: none;
}
.logo img {
width: 60px;
vertical-align: -8px;
}
.mobile-logo {
text-align: center;
}
.sidebar {
font-size: 15px;
color: #777;
}
.sidebar a {
color: #a41211;
}
.sidebar p {
margin: 10px 0;
}
@media (max-width: 767px) {
.sidebar {
text-align: center;
margin-top: 40px;
}
.sidebar .logo {
display: none;
}
}
@media (min-width: 768px) {
.mobile-logo {
display: none;
}
.logo {
margin-top: 30px;
margin-bottom: 30px;
}
.content h1 {
margin: 60px 0 55px 0;
}
.sidebar {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 280px;
overflow-y: auto;
padding-left: 40px;
padding-right: 10px;
border-right: 1px solid #ccc;
}
.content {
margin-left: 320px;
max-width: 650px;
}
}
.nav {
margin: 15px 0;
}
.nav li a {
display: block;
padding: 5px 0;
line-height: 1.2;
text-decoration: none;
}
.nav li a:hover, .nav li a:focus {
text-decoration: underline;
background: none;
}
.nav ul {
padding-left: 20px;
list-style: none;
}
.badges {
margin: 40px 0;
}
a.btn {
background: #25594D;
color: white;
text-transform: uppercase;
text-decoration: none;
}
a.btn:hover {
color: white;
}
.strapline {
font-size: 30px;
}
@media (min-width: 768px) {
.strapline {
font-size: 40px;
display: block;
line-height: 1.2;
margin-top: 25px;
margin-bottom: 35px;
}
}
strong {
font-weight: 700;
}

View File

@@ -1,18 +1,17 @@
---
layout: default
title: Getting started with Fig and Django
title: Getting started with Compose and Django
---
Getting started with Fig and Django
Getting started with Compose and Django
===================================
Let's use Fig to set up and run a Django/PostgreSQL app. Before starting, you'll need to have [Fig installed](install.html).
Let's use Compose to set up and run a Django/PostgreSQL app. Before starting, you'll need to have [Compose installed](install.md).
Let's set up the three files that'll get us started. First, our app is going to be running inside a Docker container which contains all of its dependencies. We can define what goes inside that Docker container using a file called `Dockerfile`. It'll contain this to start with:
FROM orchardup/python:2.7
FROM python:2.7
ENV PYTHONUNBUFFERED 1
RUN apt-get update -qq && apt-get install -y python-psycopg2
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
@@ -24,11 +23,12 @@ That'll install our application inside an image with Python installed alongside
Second, we define our Python dependencies in a file called `requirements.txt`:
Django
psycopg2
Simple enough. Finally, this is all tied together with a file called `fig.yml`. It describes the services that our app comprises of (a web server and database), what Docker images they use, how they link together, what volumes will be mounted inside the containers and what ports they expose.
Simple enough. Finally, this is all tied together with a file called `docker-compose.yml`. It describes the services that our app comprises of (a web server and database), what Docker images they use, how they link together, what volumes will be mounted inside the containers and what ports they expose.
db:
image: orchardup/postgresql
image: postgres
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
@@ -39,35 +39,34 @@ Simple enough. Finally, this is all tied together with a file called `fig.yml`.
links:
- db
See the [`fig.yml` reference](http://orchardup.github.io/fig/yml.html) for more information on how it works.
See the [`docker-compose.yml` reference](yml.html) for more information on how it works.
We can now start a Django project using `fig run`:
We can now start a Django project using `docker-compose run`:
$ fig run web django-admin.py startproject figexample .
$ docker-compose run web django-admin.py startproject composeexample .
First, Fig will build an image for the `web` service using the `Dockerfile`. It will then run `django-admin.py startproject figexample .` inside a container using that image.
First, Compose will build an image for the `web` service using the `Dockerfile`. It will then run `django-admin.py startproject composeexample .` inside a container using that image.
This will generate a Django app inside the current directory:
$ ls
Dockerfile fig.yml figexample manage.py requirements.txt
Dockerfile docker-compose.yml composeexample manage.py requirements.txt
First thing we need to do is set up the database connection. Replace the `DATABASES = ...` definition in `figexample/settings.py` to read:
First thing we need to do is set up the database connection. Replace the `DATABASES = ...` definition in `composeexample/settings.py` to read:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'docker',
'USER': 'docker',
'PASSWORD': 'docker',
'HOST': os.environ.get('DB_1_PORT_5432_TCP_ADDR'),
'PORT': os.environ.get('DB_1_PORT_5432_TCP_PORT'),
'NAME': 'postgres',
'USER': 'postgres',
'HOST': 'db',
'PORT': 5432,
}
}
These settings are determined by the [orchardup/postgresql](https://github.com/orchardup/docker-postgresql) Docker image we are using.
These settings are determined by the [postgres](https://registry.hub.docker.com/_/postgres/) Docker image we are using.
Then, run `fig up`:
Then, run `docker-compose up`:
Recreating myapp_db_1...
Recreating myapp_web_1...
@@ -80,13 +79,21 @@ Then, run `fig up`:
myapp_web_1 |
myapp_web_1 | 0 errors found
myapp_web_1 | January 27, 2014 - 12:12:40
myapp_web_1 | Django version 1.6.1, using settings 'figexample.settings'
myapp_web_1 | Django version 1.6.1, using settings 'composeexample.settings'
myapp_web_1 | Starting development server at http://0.0.0.0:8000/
myapp_web_1 | Quit the server with CONTROL-C.
And your Django app should be running at [localhost:8000](http://localhost:8000) (or [localdocker:8000](http://localdocker:8000) if you're using docker-osx).
And your Django app should be running at port 8000 on your docker daemon (if you're using boot2docker, `boot2docker ip` will tell you its address).
You can also run management commands with Docker. To set up your database, for example, run `fig up` and in another terminal run:
You can also run management commands with Docker. To set up your database, for example, run `docker-compose up` and in another terminal run:
$ fig run web python manage.py syncdb
$ docker-compose run web python manage.py syncdb
## Compose documentation
- [Installing Compose](install.md)
- [User guide](index.md)
- [Command line reference](cli.md)
- [Yaml file reference](yml.md)
- [Compose environment variables](env.md)
- [Compose command line completion](completion.md)

View File

@@ -1,31 +1,41 @@
---
layout: default
title: Fig environment variables reference
title: Compose environment variables reference
---
Environment variables reference
===============================
Fig uses [Docker links] to expose services' containers to one another. Each linked container injects a set of environment variables, each of which begins with the uppercase name of the container.
**Note:** Environment variables are no longer the recommended method for connecting to linked services. Instead, you should use the link name (by default, the name of the linked service) as the hostname to connect to. See the [docker-compose.yml documentation](yml.md#links) for details.
To see what environment variables are available to a service, run `fig run SERVICE env`.
Compose uses [Docker links] to expose services' containers to one another. Each linked container injects a set of environment variables, each of which begins with the uppercase name of the container.
To see what environment variables are available to a service, run `docker-compose run SERVICE env`.
<b><i>name</i>\_PORT</b><br>
Full URL, e.g. `DB_1_PORT=tcp://172.17.0.5:5432`
Full URL, e.g. `DB_PORT=tcp://172.17.0.5:5432`
<b><i>name</i>\_PORT\_<i>num</i>\_<i>protocol</i></b><br>
Full URL, e.g. `DB_1_PORT_5432_TCP=tcp://172.17.0.5:5432`
Full URL, e.g. `DB_PORT_5432_TCP=tcp://172.17.0.5:5432`
<b><i>name</i>\_PORT\_<i>num</i>\_<i>protocol</i>\_ADDR</b><br>
Container's IP address, e.g. `DB_1_PORT_5432_TCP_ADDR=172.17.0.5`
Container's IP address, e.g. `DB_PORT_5432_TCP_ADDR=172.17.0.5`
<b><i>name</i>\_PORT\_<i>num</i>\_<i>protocol</i>\_PORT</b><br>
Exposed port number, e.g. `DB_1_PORT_5432_TCP_PORT=5432`
Exposed port number, e.g. `DB_PORT_5432_TCP_PORT=5432`
<b><i>name</i>\_PORT\_<i>num</i>\_<i>protocol</i>\_PROTO</b><br>
Protocol (tcp or udp), e.g. `DB_1_PORT_5432_TCP_PROTO=tcp`
Protocol (tcp or udp), e.g. `DB_PORT_5432_TCP_PROTO=tcp`
<b><i>name</i>\_NAME</b><br>
Fully qualified container name, e.g. `DB_1_NAME=/myapp_web_1/myapp_db_1`
[Docker links]: http://docs.docker.io/en/latest/use/port_redirection/#linking-a-container
[Docker links]: http://docs.docker.com/userguide/dockerlinks/
## Compose documentation
- [Installing Compose](install.md)
- [User guide](index.md)
- [Command line reference](cli.md)
- [Yaml file reference](yml.md)
- [Compose command line completion](completion.md)

View File

@@ -1,8 +0,0 @@
jekyll:
build: .
ports:
- "4000:4000"
volumes:
- .:/code
environment:
- LANG=en_US.UTF-8

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -1,68 +1,88 @@
---
layout: default
title: Fig | Fast, isolated development environments using Docker
---
page_title: Compose: Multi-container orchestration for Docker
page_description: Introduction and Overview of Compose
page_keywords: documentation, docs, docker, compose, orchestration, containers
<strong class="strapline">Fast, isolated development environments using Docker.</strong>
Define your app's environment with Docker so it can be reproduced anywhere:
# Docker Compose
FROM orchardup/python:2.7
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
Compose is a tool for defining and running complex applications with Docker.
With Compose, you define a multi-container application in a single file, then
spin your application up in a single command which does everything that needs to
be done to get it running.
Define the services that make up your app so they can be run together in an isolated environment:
Compose is great for development environments, staging servers, and CI. We don't
recommend that you use it in production yet.
Using Compose is basically a three-step process.
First, you define your app's environment with a `Dockerfile` so it can be
reproduced anywhere:
```Dockerfile
FROM python:2.7
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code
CMD python app.py
```
Next, you define the services that make up your app in `docker-compose.yml` so
they can be run together in an isolated environment:
```yaml
web:
build: .
command: python app.py
links:
- db
ports:
- "8000:8000"
db:
image: orchardup/postgresql
image: postgres
```
(No more installing Postgres on your laptop!)
Lastly, run `docker-compose up` and Compose will start and run your entire app.
Then type `fig up`, and Fig will start and run your entire app:
Compose has commands for managing the whole lifecycle of your application:
![example fig run](https://orchardup.com/static/images/fig-example-large.gif)
* Start, stop and rebuild services
* View the status of running services
* Stream the log output of running services
* Run a one-off command on a service
There are commands to:
## Compose documentation
- start, stop and rebuild services
- view the status of running services
- tail running services' log output
- run a one-off command on a service
- [Installing Compose](install.md)
- [Command line reference](cli.md)
- [Yaml file reference](yml.md)
- [Compose environment variables](env.md)
- [Compose command line completion](completion.md)
## Quick start
Quick start
-----------
Let's get started with a walkthrough of getting a simple Python web app running
on Compose. It assumes a little knowledge of Python, but the concepts
demonstrated here should be understandable even if you're not familiar with
Python.
Let's get a basic Python web app running on Fig. It assumes a little knowledge of Python, but the concepts should be clear if you're not familiar with it.
### Installation and set-up
First, [install Docker and Fig](install.html).
First, [install Docker and Compose](install.md).
You'll want to make a directory for the project:
Next, you'll want to make a directory for the project:
$ mkdir figtest
$ cd figtest
$ mkdir composetest
$ cd composetest
Inside this directory, create `app.py`, a simple web app that uses the Flask framework and increments a value in Redis:
Inside this directory, create `app.py`, a simple web app that uses the Flask
framework and increments a value in Redis:
```python
from flask import Flask
from redis import Redis
import os
app = Flask(__name__)
redis = Redis(
host=os.environ.get('REDIS_1_PORT_6379_TCP_ADDR'),
port=int(os.environ.get('REDIS_1_PORT_6379_TCP_PORT'))
)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
@@ -73,21 +93,32 @@ if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
```
We define our Python dependencies in a file called `requirements.txt`:
Next, define the Python dependencies in a file called `requirements.txt`:
flask
redis
Next, we want to create a Docker image containing all of our app's dependencies. We specify how to build one using a file called `Dockerfile`:
### Create a Docker image
FROM orchardup/python:2.7
Now, create a Docker image containing all of your app's dependencies. You
specify how to build the image using a file called
[`Dockerfile`](http://docs.docker.com/reference/builder/):
FROM python:2.7
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
This tells Docker to install Python, our code and our Python dependencies inside a Docker image. For more information on how to write Dockerfiles, see the [Docker user guide](https://docs.docker.com/userguide/dockerimages/#building-an-image-from-a-dockerfile) and the [Dockerfile reference](http://docs.docker.com/reference/builder/).
This tells Docker to include Python, your code, and your Python dependencies in
a Docker image. For more information on how to write Dockerfiles, see the
[Docker user
guide](https://docs.docker.com/userguide/dockerimages/#building-an-image-from-a-dockerfile)
and the
[Dockerfile reference](http://docs.docker.com/reference/builder/).
We then define a set of services using `fig.yml`:
### Define services
Next, define a set of services using `docker-compose.yml`:
web:
build: .
@@ -99,45 +130,61 @@ We then define a set of services using `fig.yml`:
links:
- redis
redis:
image: orchardup/redis
image: redis
This defines two services:
- `web`, which is built from `Dockerfile` in the current directory. It also says to run the command `python app.py` inside the image, forward the exposed port 5000 on the container to port 5000 on the host machine, connect up the Redis service, and mount the current directory inside the container so we can work on code without having to rebuild the image.
- `redis`, which uses the public image [orchardup/redis](https://index.docker.io/u/orchardup/redis/).
- `web`, which is built from the `Dockerfile` in the current directory. It also
says to run the command `python app.py` inside the image, forward the exposed
port 5000 on the container to port 5000 on the host machine, connect up the
Redis service, and mount the current directory inside the container so we can
work on code without having to rebuild the image.
- `redis`, which uses the public image
[redis](https://registry.hub.docker.com/_/redis/), which gets pulled from the
Docker Hub registry.
Now if we run `fig up`, it'll pull a Redis image, build an image for our own code, and start everything up:
### Build and run your app with Compose
$ fig up
Pulling image orchardup/redis...
Now, when you run `docker-compose up`, Compose will pull a Redis image, build an
image for your code, and start everything up:
$ docker-compose up
Pulling image redis...
Building web...
Starting figtest_redis_1...
Starting figtest_web_1...
figtest_redis_1 | [8] 02 Jan 18:43:35.576 # Server started, Redis version 2.8.3
figtest_web_1 | * Running on http://0.0.0.0:5000/
Starting composetest_redis_1...
Starting composetest_web_1...
redis_1 | [8] 02 Jan 18:43:35.576 # Server started, Redis version 2.8.3
web_1 | * Running on http://0.0.0.0:5000/
Open up [http://localhost:5000](http://localhost:5000) in your browser (or [http://localdocker:5000](http://localdocker:5000) if you're using [docker-osx](https://github.com/noplay/docker-osx)) and you should see it running!
The web app should now be listening on port 5000 on your Docker daemon host (if
you're using Boot2docker, `boot2docker ip` will tell you its address).
If you want to run your services in the background, you can pass the `-d` flag to `fig up` and use `fig ps` to see what is currently running:
If you want to run your services in the background, you can pass the `-d` flag
(for daemon mode) to `docker-compose up` and use `docker-compose ps` to see what
is currently running:
$ fig up -d
Starting figtest_redis_1...
Starting figtest_web_1...
$ fig ps
Name Command State Ports
$ docker-compose up -d
Starting composetest_redis_1...
Starting composetest_web_1...
$ docker-compose ps
Name Command State Ports
-------------------------------------------------------------------
figtest_redis_1 /usr/local/bin/run Up
figtest_web_1 /bin/sh -c python app.py Up 5000->5000/tcp
composetest_redis_1 /usr/local/bin/run Up
composetest_web_1 /bin/sh -c python app.py Up 5000->5000/tcp
`fig run` allows you to run one-off commands for your services. For example, to see what environment variables are available to the `web` service:
The `docker-compose run` command allows you to run one-off commands for your
services. For example, to see what environment variables are available to the
`web` service:
$ fig run web env
$ docker-compose run web env
See `docker-compose --help` to see other available commands.
If you started Compose with `docker-compose up -d`, you'll probably want to stop
your services once you've finished with them:
$ docker-compose stop
At this point, you have seen the basics of how Compose works.
See `fig --help` other commands that are available.
If you started Fig with `fig up -d`, you'll probably want to stop your services once you've finished with them:
$ fig stop
That's more-or-less how Fig works. See the reference section below for full details on the commands, configuration file and environment variables. If you have any thoughts or suggestions, [open an issue on GitHub](https://github.com/orchardup/fig) or [email us](mailto:hello@orchardup.com).

View File

@@ -1,31 +1,52 @@
---
layout: default
title: Installing Fig
---
page_title: Installing Compose
page_description: How to intall Docker Compose
page_keywords: compose, orchestration, install, installation, docker, documentation
Installing Fig
==============
First, install Docker version 1.0.0. If you're on OS X, you can use [docker-osx](https://github.com/noplay/docker-osx):
## Installing Compose
$ curl https://raw.githubusercontent.com/noplay/docker-osx/1.0.0/docker-osx > /usr/local/bin/docker-osx
$ chmod +x /usr/local/bin/docker-osx
$ docker-osx shell
To install Compose, you'll need to install Docker first. You'll then install
Compose with a `curl` command.
Docker has guides for [Ubuntu](http://docs.docker.io/en/latest/installation/ubuntulinux/) and [other platforms](http://docs.docker.io/en/latest/installation/) in their documentation.
### Install Docker
Next, install Fig. On OS X:
First, you'll need to install Docker version 1.3 or greater.
$ curl -L https://github.com/orchardup/fig/releases/download/0.4.2/darwin > /usr/local/bin/fig
$ chmod +x /usr/local/bin/fig
If you're on OS X, you can use the
[OS X installer](https://docs.docker.com/installation/mac/) to install both
Docker and the OSX helper app, boot2docker. Once boot2docker is running, set the
environment variables that'll configure Docker and Compose to talk to it:
On 64-bit Linux:
$(boot2docker shellinit)
$ curl -L https://github.com/orchardup/fig/releases/download/0.4.2/linux > /usr/local/bin/fig
$ chmod +x /usr/local/bin/fig
To persist the environment variables across shell sessions, add the above line
to your `~/.bashrc` file.
Fig is also available as a Python package if you're on another platform (or if you prefer that sort of thing):
For complete instructions, or if you are on another platform, consult Docker's
[installation instructions](https://docs.docker.com/installation/).
$ sudo pip install -U fig
### Install Compose
That should be all you need! Run `fig --version` to see if it worked.
To install Compose, run the following commands:
curl -L https://github.com/docker/compose/releases/download/1.1.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
Optionally, you can also install [command completion](completion.md) for the
bash shell.
Compose is available for OS X and 64-bit Linux. If you're on another platform,
Compose can also be installed as a Python package:
$ sudo pip install -U docker-compose
No further steps are required; Compose should now be successfully installed.
You can test the installation by running `docker-compose --version`.
## Compose documentation
- [User guide](index.md)
- [Command line reference](cli.md)
- [Yaml file reference](yml.md)
- [Compose environment variables](env.md)
- [Compose command line completion](completion.md)

7
docs/mkdocs.yml Normal file
View File

@@ -0,0 +1,7 @@
- ['compose/index.md', 'User Guide', 'Docker Compose' ]
- ['compose/install.md', 'Installation', 'Docker Compose']
- ['compose/cli.md', 'Reference', 'Compose command line']
- ['compose/yml.md', 'Reference', 'Compose yml']
- ['compose/env.md', 'Reference', 'Compose ENV variables']
- ['compose/completion.md', 'Reference', 'Compose commandline completion']

View File

@@ -1,16 +1,16 @@
---
layout: default
title: Getting started with Fig and Rails
title: Getting started with Compose and Rails
---
Getting started with Fig and Rails
Getting started with Compose and Rails
==================================
We're going to use Fig to set up and run a Rails/PostgreSQL app. Before starting, you'll need to have [Fig installed](install.html).
We're going to use Compose to set up and run a Rails/PostgreSQL app. Before starting, you'll need to have [Compose installed](install.md).
Let's set up the three files that'll get us started. First, our app is going to be running inside a Docker container which contains all of its dependencies. We can define what goes inside that Docker container using a file called `Dockerfile`. It'll contain this to start with:
FROM binaryphile/ruby:2.0.0-p247
FROM ruby:2.2.0
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev
RUN mkdir /myapp
WORKDIR /myapp
@@ -23,17 +23,17 @@ That'll put our application code inside an image with Ruby, Bundler and all our
Next, we have a bootstrap `Gemfile` which just loads Rails. It'll be overwritten in a moment by `rails new`.
source 'https://rubygems.org'
gem 'rails', '4.0.2'
gem 'rails', '4.2.0'
Finally, `fig.yml` is where the magic happens. It describes what services our app comprises (a database and a web app), how to get each one's Docker image (the database just runs on a pre-made PostgreSQL image, and the web app is built from the current directory), and the configuration we need to link them together and expose the web app's port.
Finally, `docker-compose.yml` is where the magic happens. It describes what services our app comprises (a database and a web app), how to get each one's Docker image (the database just runs on a pre-made PostgreSQL image, and the web app is built from the current directory), and the configuration we need to link them together and expose the web app's port.
db:
image: orchardup/postgresql
image: postgres
ports:
- "5432"
web:
build: .
command: bundle exec rackup -p 3000
command: bundle exec rails s -p 3000 -b '0.0.0.0'
volumes:
- .:/myapp
ports:
@@ -41,14 +41,14 @@ Finally, `fig.yml` is where the magic happens. It describes what services our ap
links:
- db
With those files in place, we can now generate the Rails skeleton app using `fig run`:
With those files in place, we can now generate the Rails skeleton app using `docker-compose run`:
$ fig run web rails new . --force --database=postgresql --skip-bundle
$ docker-compose run web rails new . --force --database=postgresql --skip-bundle
First, Fig will build the image for the `web` service using the `Dockerfile`. Then it'll run `rails new` inside a new container, using that image. Once it's done, you should have a fresh app generated:
First, Compose will build the image for the `web` service using the `Dockerfile`. Then it'll run `rails new` inside a new container, using that image. Once it's done, you should have a fresh app generated:
$ ls
Dockerfile app fig.yml tmp
Dockerfile app docker-compose.yml tmp
Gemfile bin lib vendor
Gemfile.lock config log
README.rdoc config.ru public
@@ -60,21 +60,20 @@ Uncomment the line in your new `Gemfile` which loads `therubyracer`, so we've go
Now that we've got a new `Gemfile`, we need to build the image again. (This, and changes to the Dockerfile itself, should be the only times you'll need to rebuild).
$ fig build
$ docker-compose build
The app is now bootable, but we're not quite there yet. By default, Rails expects a database to be running on `localhost` - we need to point it at the `db` container instead. We also need to change the username and password to align with the defaults set by `orchardup/postgresql`.
The app is now bootable, but we're not quite there yet. By default, Rails expects a database to be running on `localhost` - we need to point it at the `db` container instead. We also need to change the database and username to align with the defaults set by the `postgres` image.
Open up your newly-generated `database.yml`. Replace its contents with the following:
development: &default
adapter: postgresql
encoding: unicode
database: myapp_development
database: postgres
pool: 5
username: docker
password: docker
host: <%= ENV.fetch('DB_1_PORT_5432_TCP_ADDR', 'localhost') %>
port: <%= ENV.fetch('DB_1_PORT_5432_TCP_PORT', '5432') %>
username: postgres
password:
host: db
test:
<<: *default
@@ -82,18 +81,25 @@ Open up your newly-generated `database.yml`. Replace its contents with the follo
We can now boot the app.
$ fig up
$ docker-compose up
If all's well, you should see some PostgreSQL output, and then—after a few seconds—the familiar refrain:
myapp_web_1 | [2014-01-17 17:16:29] INFO WEBrick 1.3.1
myapp_web_1 | [2014-01-17 17:16:29] INFO ruby 2.0.0 (2013-11-22) [x86_64-linux-gnu]
myapp_web_1 | [2014-01-17 17:16:29] INFO ruby 2.2.0 (2014-12-25) [x86_64-linux-gnu]
myapp_web_1 | [2014-01-17 17:16:29] INFO WEBrick::HTTPServer#start: pid=1 port=3000
Finally, we just need to create the database. In another terminal, run:
$ fig run web rake db:create
$ docker-compose run web rake db:create
And we're rolling—see for yourself at [localhost:3000](http://localhost:3000) (or [localdocker:3000](http://localdocker:3000) if you're using docker-osx).
And we're rolling—your app should now be running on port 3000 on your docker daemon (if you're using boot2docker, `boot2docker ip` will tell you its address).
![Screenshot of Rails' stock index.html](https://orchardup.com/static/images/fig-rails-screenshot.png)
## Compose documentation
- [Installing Compose](install.md)
- [User guide](index.md)
- [Command line reference](cli.md)
- [Yaml file reference](yml.md)
- [Compose environment variables](env.md)
- [Compose command line completion](completion.md)

View File

@@ -1,14 +1,14 @@
---
layout: default
title: Getting started with Fig and Wordpress
title: Getting started with Compose and Wordpress
---
Getting started with Fig and Wordpress
Getting started with Compose and Wordpress
======================================
Fig makes it nice and easy to run Wordpress in an isolated environment. [Install Fig](install.html), then download Wordpress into the current directory:
Compose makes it nice and easy to run Wordpress in an isolated environment. [Install Compose](install.md), then download Wordpress into the current directory:
$ curl http://wordpress.org/wordpress-3.8.1.tar.gz | tar -xvzf -
$ curl https://wordpress.org/latest.tar.gz | tar -xvzf -
This will create a directory called `wordpress`, which you can rename to the name of your project if you wish. Inside that directory, we create `Dockerfile`, a file that defines what environment your app is going to run in:
@@ -19,7 +19,7 @@ ADD . /code
This instructs Docker on how to build an image that contains PHP and Wordpress. For more information on how to write Dockerfiles, see the [Docker user guide](https://docs.docker.com/userguide/dockerimages/#building-an-image-from-a-dockerfile) and the [Dockerfile reference](http://docs.docker.com/reference/builder/).
Next up, `fig.yml` starts our web service and a separate MySQL instance:
Next up, `docker-compose.yml` starts our web service and a separate MySQL instance:
```
web:
@@ -37,14 +37,14 @@ db:
MYSQL_DATABASE: wordpress
```
Two supporting files are needed to get this working - first up, `wp-config.php` is the standard Wordpress config file with a single change to make it read the MySQL host and port from the environment variables passed in by Fig:
Two supporting files are needed to get this working - first up, `wp-config.php` is the standard Wordpress config file with a single change to point the database configuration at the `db` container:
```
<?php
define('DB_NAME', 'wordpress');
define('DB_USER', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', getenv("DB_1_PORT_3306_TCP_ADDR") . ":" . getenv("DB_1_PORT_3306_TCP_PORT"));
define('DB_HOST', "db:3306");
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
@@ -88,4 +88,13 @@ if(file_exists($root.$path))
}else include_once 'index.php';
```
With those four files in place, run `fig up` inside your Wordpress directory and it'll pull and build the images we need, and then start the web and database containers. You'll then be able to visit Wordpress and set it up by visiting [localhost:8000](http://localhost:8000) - or [localdocker:8000](http://localdocker:8000) if you're using docker-osx.
With those four files in place, run `docker-compose up` inside your Wordpress directory and it'll pull and build the images we need, and then start the web and database containers. You'll then be able to visit Wordpress at port 8000 on your docker daemon (if you're using boot2docker, `boot2docker ip` will tell you its address).
## Compose documentation
- [Installing Compose](install.md)
- [User guide](index.md)
- [Command line reference](cli.md)
- [Yaml file reference](yml.md)
- [Compose environment variables](env.md)
- [Compose command line completion](completion.md)

View File

@@ -1,61 +1,249 @@
---
layout: default
title: fig.yml reference
title: docker-compose.yml reference
page_title: docker-compose.yml reference
page_description: docker-compose.yml reference
page_keywords: fig, composition, compose, docker
---
fig.yml reference
=================
# docker-compose.yml reference
Each service defined in `fig.yml` must specify exactly one of `image` or `build`. Other keys are optional, and are analogous to their `docker run` command-line counterparts.
Each service defined in `docker-compose.yml` must specify exactly one of
`image` or `build`. Other keys are optional, and are analogous to their
`docker run` command-line counterparts.
As with `docker run`, options specified in the Dockerfile (e.g. `CMD`, `EXPOSE`, `VOLUME`, `ENV`) are respected by default - you don't need to specify them again in `fig.yml`.
As with `docker run`, options specified in the Dockerfile (e.g., `CMD`,
`EXPOSE`, `VOLUME`, `ENV`) are respected by default - you don't need to
specify them again in `docker-compose.yml`.
```yaml
-- Tag or partial image ID. Can be local or remote - Fig will attempt to pull
-- if it doesn't exist locally.
### image
Tag or partial image ID. Can be local or remote - Compose will attempt to
pull if it doesn't exist locally.
```
image: ubuntu
image: orchardup/postgresql
image: a4bc65fd
```
-- Path to a directory containing a Dockerfile. Fig will build and tag it with
-- a generated name, and use that image thereafter.
### build
Path to a directory containing a Dockerfile. This directory is also the
build context that is sent to the Docker daemon.
Compose will build and tag it with a generated name, and use that image thereafter.
```
build: /path/to/build/dir
```
-- Override the default command.
### command
Override the default command.
```
command: bundle exec thin -p 3000
```
-- Link to containers in another service. Optionally specify an alternate name
-- for the link, which will determine how environment variables are prefixed,
-- e.g. "db" -> DB_1_PORT, "db:database" -> DATABASE_1_PORT
<a name="links"></a>
### links
Link to containers in another service. Either specify both the service name and
the link alias (`SERVICE:ALIAS`), or just the service name (which will also be
used for the alias).
```
links:
- db
- db:database
- redis
```
-- Expose ports. Either specify both ports (HOST:CONTAINER), or just the
-- container port (a random host port will be chosen).
-- Note: When mapping ports in the HOST:CONTAINER format, you may experience
-- erroneous results when using a container port lower than 60, because YAML
-- will parse numbers in the format "xx:yy" as sexagesimal (base 60). For
-- this reason, we recommend always explicitly specifying your port mappings
-- as strings.
An entry with the alias' name will be created in `/etc/hosts` inside containers
for this service, e.g:
```
172.17.2.186 db
172.17.2.186 database
172.17.2.187 redis
```
Environment variables will also be created - see the [environment variable
reference](env.md) for details.
### external_links
Link to containers started outside this `docker-compose.yml` or even outside
of Compose, especially for containers that provide shared or common services.
`external_links` follow semantics similar to `links` when specifying both the
container name and the link alias (`CONTAINER:ALIAS`).
```
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
```
### ports
Expose ports. Either specify both ports (`HOST:CONTAINER`), or just the container
port (a random host port will be chosen).
> **Note:** When mapping ports in the `HOST:CONTAINER` format, you may experience
> erroneous results when using a container port lower than 60, because YAML will
> parse numbers in the format `xx:yy` as sexagesimal (base 60). For this reason,
> we recommend always explicitly specifying your port mappings as strings.
```
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
```
-- Expose ports without publishing them to the host machine - they'll only be
-- accessible to linked services. Only the internal port can be specified.
### expose
Expose ports without publishing them to the host machine - they'll only be
accessible to linked services. Only the internal port can be specified.
```
expose:
- "3000"
- "8000"
-- Map volumes from the host machine (HOST:CONTAINER).
volumes:
- cache/:/tmp/cache
-- Add environment variables.
environment:
RACK_ENV: development
```
### volumes
Mount paths as volumes, optionally specifying a path on the host machine
(`HOST:CONTAINER`), or an access mode (`HOST:CONTAINER:ro`).
```
volumes:
- /var/lib/mysql
- cache/:/tmp/cache
- ~/configs:/etc/configs/:ro
```
### volumes_from
Mount all of the volumes from another service or container.
```
volumes_from:
- service_name
- container_name
```
### environment
Add environment variables. You can use either an array or a dictionary.
Environment variables with only a key are resolved to their values on the
machine Compose is running on, which can be helpful for secret or host-specific values.
```
environment:
RACK_ENV: development
SESSION_SECRET:
environment:
- RACK_ENV=development
- SESSION_SECRET
```
### env_file
Add environment variables from a file. Can be a single value or a list.
Environment variables specified in `environment` override these values.
```
env_file:
- .env
```
```
RACK_ENV: development
```
### net
Networking mode. Use the same values as the docker client `--net` parameter.
```
net: "bridge"
net: "none"
net: "container:[name or id]"
net: "host"
```
### dns
Custom DNS servers. Can be a single value or a list.
```
dns: 8.8.8.8
dns:
- 8.8.8.8
- 9.9.9.9
```
### cap_add, cap_drop
Add or drop container capabilities.
See `man 7 capabilities` for a full list.
```
cap_add:
- ALL
cap_drop:
- NET_ADMIN
- SYS_ADMIN
```
### dns_search
Custom DNS search domains. Can be a single value or a list.
```
dns_search: example.com
dns_search:
- dc1.example.com
- dc2.example.com
```
### working\_dir, entrypoint, user, hostname, domainname, mem\_limit, privileged, restart, stdin\_open, tty, cpu\_shares
Each of these is a single value, analogous to its
[docker run](https://docs.docker.com/reference/run/) counterpart.
```
cpu_shares: 73
working_dir: /code
entrypoint: /code/entrypoint.sh
user: postgresql
hostname: foo
domainname: foo.com
mem_limit: 1000000000
privileged: true
restart: always
stdin_open: true
tty: true
```
## Compose documentation
- [Installing Compose](install.md)
- [User guide](index.md)
- [Command line reference](cli.md)
- [Compose environment variables](env.md)
- [Compose command line completion](completion.md)

View File

@@ -1,4 +0,0 @@
from __future__ import unicode_literals
from .service import Service
__version__ = '0.4.2'

View File

@@ -1,95 +0,0 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from ..packages.docker import Client
from requests.exceptions import ConnectionError
import errno
import logging
import os
import re
import yaml
from ..packages import six
import sys
from ..project import Project
from ..service import ConfigError
from .docopt_command import DocoptCommand
from .formatter import Formatter
from .utils import cached_property, docker_url, call_silently, is_mac, is_ubuntu
from . import errors
log = logging.getLogger(__name__)
class Command(DocoptCommand):
base_dir = '.'
def __init__(self):
self.yaml_path = os.environ.get('FIG_FILE', None)
self.explicit_project_name = None
def dispatch(self, *args, **kwargs):
try:
super(Command, self).dispatch(*args, **kwargs)
except ConnectionError:
if call_silently(['which', 'docker']) != 0:
if is_mac():
raise errors.DockerNotFoundMac()
elif is_ubuntu():
raise errors.DockerNotFoundUbuntu()
else:
raise errors.DockerNotFoundGeneric()
elif call_silently(['which', 'docker-osx']) == 0:
raise errors.ConnectionErrorDockerOSX()
else:
raise errors.ConnectionErrorGeneric(self.client.base_url)
def perform_command(self, options, *args, **kwargs):
if options['--file'] is not None:
self.yaml_path = os.path.join(self.base_dir, options['--file'])
if options['--project-name'] is not None:
self.explicit_project_name = options['--project-name']
return super(Command, self).perform_command(options, *args, **kwargs)
@cached_property
def client(self):
return Client(docker_url())
@cached_property
def project(self):
try:
yaml_path = self.yaml_path
if yaml_path is None:
yaml_path = self.check_yaml_filename()
config = yaml.load(open(yaml_path))
except IOError as e:
if e.errno == errno.ENOENT:
raise errors.FigFileNotFound(os.path.basename(e.filename))
raise errors.UserError(six.text_type(e))
try:
return Project.from_config(self.project_name, config, self.client)
except ConfigError as e:
raise errors.UserError(six.text_type(e))
@cached_property
def project_name(self):
project = os.path.basename(os.getcwd())
if self.explicit_project_name is not None:
project = self.explicit_project_name
project = re.sub(r'[^a-zA-Z0-9]', '', project)
if not project:
project = 'default'
return project
@cached_property
def formatter(self):
return Formatter()
def check_yaml_filename(self):
if os.path.exists(os.path.join(self.base_dir, 'fig.yaml')):
log.warning("Fig just read the file 'fig.yaml' on startup, rather than 'fig.yml'")
log.warning("Please be aware that fig.yml the expected extension in most cases, and using .yaml can cause compatibility issues in future")
return os.path.join(self.base_dir, 'fig.yaml')
else:
return os.path.join(self.base_dir, 'fig.yml')

View File

@@ -1,336 +0,0 @@
from __future__ import print_function
from __future__ import unicode_literals
import logging
import sys
import re
import signal
from inspect import getdoc
from .. import __version__
from ..project import NoSuchService, ConfigurationError
from ..service import BuildError, CannotBeScaledError
from .command import Command
from .formatter import Formatter
from .log_printer import LogPrinter
from .utils import yesno
from ..packages.docker.errors import APIError
from .errors import UserError
from .docopt_command import NoSuchCommand
from .socketclient import SocketClient
log = logging.getLogger(__name__)
def main():
console_handler = logging.StreamHandler(stream=sys.stderr)
console_handler.setFormatter(logging.Formatter())
console_handler.setLevel(logging.INFO)
root_logger = logging.getLogger()
root_logger.addHandler(console_handler)
root_logger.setLevel(logging.DEBUG)
# Disable requests logging
logging.getLogger("requests").propagate = False
try:
command = TopLevelCommand()
command.sys_dispatch()
except KeyboardInterrupt:
log.error("\nAborting.")
sys.exit(1)
except (UserError, NoSuchService, ConfigurationError) as e:
log.error(e.msg)
sys.exit(1)
except NoSuchCommand as e:
log.error("No such command: %s", e.command)
log.error("")
log.error("\n".join(parse_doc_section("commands:", getdoc(e.supercommand))))
sys.exit(1)
except APIError as e:
log.error(e.explanation)
sys.exit(1)
except BuildError as e:
log.error("Service '%s' failed to build: %s" % (e.service.name, e.reason))
sys.exit(1)
# stolen from docopt master
def parse_doc_section(name, source):
pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)',
re.IGNORECASE | re.MULTILINE)
return [s.strip() for s in pattern.findall(source)]
class TopLevelCommand(Command):
"""Punctual, lightweight development environments using Docker.
Usage:
fig [options] [COMMAND] [ARGS...]
fig -h|--help
Options:
--verbose Show more output
--version Print version and exit
-f, --file FILE Specify an alternate fig file (default: fig.yml)
-p, --project-name NAME Specify an alternate project name (default: directory name)
Commands:
build Build or rebuild services
help Get help on a command
kill Kill containers
logs View output from containers
ps List containers
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
up Create and start containers
"""
def docopt_options(self):
options = super(TopLevelCommand, self).docopt_options()
options['version'] = "fig %s" % __version__
return options
def build(self, options):
"""
Build or rebuild services.
Services are built once and then tagged as `project_service`,
e.g. `figtest_db`. If you change a service's `Dockerfile` or the
contents of its build directory, you can run `fig build` to rebuild it.
Usage: build [SERVICE...]
"""
self.project.build(service_names=options['SERVICE'])
def help(self, options):
"""
Get help on a command.
Usage: help COMMAND
"""
command = options['COMMAND']
if not hasattr(self, command):
raise NoSuchCommand(command, self)
raise SystemExit(getdoc(getattr(self, command)))
def kill(self, options):
"""
Force stop service containers.
Usage: kill [SERVICE...]
"""
self.project.kill(service_names=options['SERVICE'])
def logs(self, options):
"""
View output from containers.
Usage: logs [SERVICE...]
"""
containers = self.project.containers(service_names=options['SERVICE'], stopped=True)
print("Attaching to", list_containers(containers))
LogPrinter(containers, attach_params={'logs': True}).run()
def ps(self, options):
"""
List containers.
Usage: ps [options] [SERVICE...]
Options:
-q Only display IDs
"""
containers = self.project.containers(service_names=options['SERVICE'], stopped=True) + self.project.containers(service_names=options['SERVICE'], one_off=True)
if options['-q']:
for container in containers:
print(container.id)
else:
headers = [
'Name',
'Command',
'State',
'Ports',
]
rows = []
for container in containers:
command = container.human_readable_command
if len(command) > 30:
command = '%s ...' % command[:26]
rows.append([
container.name,
command,
container.human_readable_state,
container.human_readable_ports,
])
print(Formatter().table(headers, rows))
def rm(self, options):
"""
Remove stopped service containers.
Usage: rm [options] [SERVICE...]
Options:
--force Don't ask to confirm removal
-v Remove volumes associated with containers
"""
all_containers = self.project.containers(service_names=options['SERVICE'], stopped=True)
stopped_containers = [c for c in all_containers if not c.is_running]
if len(stopped_containers) > 0:
print("Going to remove", list_containers(stopped_containers))
if options.get('--force') \
or yesno("Are you sure? [yN] ", default=False):
self.project.remove_stopped(
service_names=options['SERVICE'],
v=options.get('-v', False)
)
else:
print("No stopped containers")
def run(self, options):
"""
Run a one-off command on a service.
For example:
$ fig run web python manage.py shell
Note that this will not start any services that the command's service
links to. So if, for example, your one-off command talks to your
database, you will need to run `fig up -d db` first.
Usage: run [options] SERVICE COMMAND [ARGS...]
Options:
-d Detached mode: Run container in the background, print new
container name
-T Disable pseudo-tty allocation. By default `fig run`
allocates a TTY.
--rm Remove container after run. Ignored in detached mode.
"""
service = self.project.get_service(options['SERVICE'])
tty = True
if options['-d'] or options['-T'] or not sys.stdin.isatty():
tty = False
container_options = {
'command': [options['COMMAND']] + options['ARGS'],
'tty': tty,
'stdin_open': not options['-d'],
}
container = service.create_container(one_off=True, **container_options)
if options['-d']:
service.start_container(container, ports=None, one_off=True)
print(container.name)
else:
with self._attach_to_container(container.id, raw=tty) as c:
service.start_container(container, ports=None, one_off=True)
c.run()
exit_code = container.wait()
if options['--rm']:
log.info("Removing %s..." % container.name)
self.client.remove_container(container.id)
sys.exit(exit_code)
def scale(self, options):
"""
Set number of containers to run for a service.
Numbers are specified in the form `service=num` as arguments.
For example:
$ fig scale web=2 worker=3
Usage: scale [SERVICE=NUM...]
"""
for s in options['SERVICE=NUM']:
if '=' not in s:
raise UserError('Arguments to scale should be in the form service=num')
service_name, num = s.split('=', 1)
try:
num = int(num)
except ValueError:
raise UserError('Number of containers for service "%s" is not a number' % service)
try:
self.project.get_service(service_name).scale(num)
except CannotBeScaledError:
raise UserError('Service "%s" cannot be scaled because it specifies a port on the host. If multiple containers for this service were created, the port would clash.\n\nRemove the ":" from the port definition in fig.yml so Docker can choose a random port for each container.' % service_name)
def start(self, options):
"""
Start existing containers.
Usage: start [SERVICE...]
"""
self.project.start(service_names=options['SERVICE'])
def stop(self, options):
"""
Stop running containers without removing them.
They can be started again with `fig start`.
Usage: stop [SERVICE...]
"""
self.project.stop(service_names=options['SERVICE'])
def up(self, options):
"""
Build, (re)create, start and attach to containers for a service.
By default, `fig up` will aggregate the output of each container, and
when it exits, all containers will be stopped. If you run `fig up -d`,
it'll start the containers in the background and leave them running.
If there are existing containers for a service, `fig up` will stop
and recreate them (preserving mounted volumes with volumes-from),
so that changes in `fig.yml` are picked up.
Usage: up [options] [SERVICE...]
Options:
-d Detached mode: Run containers in the background, print new
container names
"""
detached = options['-d']
to_attach = self.project.up(service_names=options['SERVICE'])
if not detached:
print("Attaching to", list_containers(to_attach))
log_printer = LogPrinter(to_attach, attach_params={"logs": True})
try:
log_printer.run()
finally:
def handler(signal, frame):
self.project.kill(service_names=options['SERVICE'])
sys.exit(0)
signal.signal(signal.SIGINT, handler)
print("Gracefully stopping... (press Ctrl+C again to force)")
self.project.stop(service_names=options['SERVICE'])
def _attach_to_container(self, container_id, raw=False):
socket_in = self.client.attach_socket(container_id, params={'stdin': 1, 'stream': 1})
socket_out = self.client.attach_socket(container_id, params={'stdout': 1, 'logs': 1, 'stream': 1})
socket_err = self.client.attach_socket(container_id, params={'stderr': 1, 'logs': 1, 'stream': 1})
return SocketClient(
socket_in=socket_in,
socket_out=socket_out,
socket_err=socket_err,
raw=raw,
)
def list_containers(containers):
return ", ".join(c.name for c in containers)

View File

@@ -1,126 +0,0 @@
from __future__ import print_function
# Adapted from https://github.com/benthor/remotty/blob/master/socketclient.py
import sys
import tty
import fcntl
import os
import termios
import threading
import errno
import logging
log = logging.getLogger(__name__)
class SocketClient:
def __init__(self,
socket_in=None,
socket_out=None,
socket_err=None,
raw=True,
):
self.socket_in = socket_in
self.socket_out = socket_out
self.socket_err = socket_err
self.raw = raw
self.stdin_fileno = sys.stdin.fileno()
def __enter__(self):
self.create()
return self
def __exit__(self, type, value, trace):
self.destroy()
def create(self):
if os.isatty(sys.stdin.fileno()):
self.settings = termios.tcgetattr(sys.stdin.fileno())
else:
self.settings = None
if self.socket_in is not None:
self.set_blocking(sys.stdin, False)
self.set_blocking(sys.stdout, True)
self.set_blocking(sys.stderr, True)
if self.raw:
tty.setraw(sys.stdin.fileno())
def set_blocking(self, file, blocking):
fd = file.fileno()
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
flags = (flags & ~os.O_NONBLOCK) if blocking else (flags | os.O_NONBLOCK)
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
def run(self):
if self.socket_in is not None:
self.start_background_thread(target=self.send, args=(self.socket_in, sys.stdin))
recv_threads = []
if self.socket_out is not None:
recv_threads.append(self.start_background_thread(target=self.recv, args=(self.socket_out, sys.stdout)))
if self.socket_err is not None:
recv_threads.append(self.start_background_thread(target=self.recv, args=(self.socket_err, sys.stderr)))
for t in recv_threads:
t.join()
def start_background_thread(self, **kwargs):
thread = threading.Thread(**kwargs)
thread.daemon = True
thread.start()
return thread
def recv(self, socket, stream):
try:
while True:
chunk = socket.recv(4096)
if chunk:
stream.write(chunk)
stream.flush()
else:
break
except Exception as e:
log.debug(e)
def send(self, socket, stream):
while True:
chunk = stream.read(1)
if chunk == '':
socket.close()
break
else:
try:
socket.send(chunk)
except Exception as e:
if hasattr(e, 'errno') and e.errno == errno.EPIPE:
break
else:
raise e
def destroy(self):
if self.settings is not None:
termios.tcsetattr(self.stdin_fileno, termios.TCSADRAIN, self.settings)
sys.stdout.flush()
if __name__ == '__main__':
import websocket
if len(sys.argv) != 2:
sys.stderr.write("Usage: python socketclient.py WEBSOCKET_URL\n")
sys.exit(1)
url = sys.argv[1]
socket = websocket.create_connection(url)
print("connected\r")
with SocketClient(socket, interactive=True) as client:
client.run()

View File

@@ -1,18 +0,0 @@
# Copyright 2013 dotCloud inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__title__ = 'docker-py'
__version__ = '0.3.0'
from .client import Client # flake8: noqa

View File

@@ -1,7 +0,0 @@
from .auth import (
INDEX_URL,
encode_header,
load_config,
resolve_authconfig,
resolve_repository_name
) # flake8: noqa

View File

@@ -1,167 +0,0 @@
# Copyright 2013 dotCloud inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import fileinput
import json
import os
from fig.packages import six
from ..utils import utils
from .. import errors
INDEX_URL = 'https://index.docker.io/v1/'
DOCKER_CONFIG_FILENAME = '.dockercfg'
def swap_protocol(url):
if url.startswith('http://'):
return url.replace('http://', 'https://', 1)
if url.startswith('https://'):
return url.replace('https://', 'http://', 1)
return url
def expand_registry_url(hostname):
if hostname.startswith('http:') or hostname.startswith('https:'):
if '/' not in hostname[9:]:
hostname = hostname + '/v1/'
return hostname
if utils.ping('https://' + hostname + '/v1/_ping'):
return 'https://' + hostname + '/v1/'
return 'http://' + hostname + '/v1/'
def resolve_repository_name(repo_name):
if '://' in repo_name:
raise errors.InvalidRepository(
'Repository name cannot contain a scheme ({0})'.format(repo_name))
parts = repo_name.split('/', 1)
if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost':
# This is a docker index repo (ex: foo/bar or ubuntu)
return INDEX_URL, repo_name
if len(parts) < 2:
raise errors.InvalidRepository(
'Invalid repository name ({0})'.format(repo_name))
if 'index.docker.io' in parts[0]:
raise errors.InvalidRepository(
'Invalid repository name, try "{0}" instead'.format(parts[1]))
return expand_registry_url(parts[0]), parts[1]
def resolve_authconfig(authconfig, registry=None):
"""Return the authentication data from the given auth configuration for a
specific registry. We'll do our best to infer the correct URL for the
registry, trying both http and https schemes. Returns an empty dictionnary
if no data exists."""
# Default to the public index server
registry = registry or INDEX_URL
# Ff its not the index server there are three cases:
#
# 1. this is a full config url -> it should be used as is
# 2. it could be a full url, but with the wrong protocol
# 3. it can be the hostname optionally with a port
#
# as there is only one auth entry which is fully qualified we need to start
# parsing and matching
if '/' not in registry:
registry = registry + '/v1/'
if not registry.startswith('http:') and not registry.startswith('https:'):
registry = 'https://' + registry
if registry in authconfig:
return authconfig[registry]
return authconfig.get(swap_protocol(registry), None)
def encode_auth(auth_info):
return base64.b64encode(auth_info.get('username', '') + b':' +
auth_info.get('password', ''))
def decode_auth(auth):
if isinstance(auth, six.string_types):
auth = auth.encode('ascii')
s = base64.b64decode(auth)
login, pwd = s.split(b':')
return login.decode('ascii'), pwd.decode('ascii')
def encode_header(auth):
auth_json = json.dumps(auth).encode('ascii')
return base64.b64encode(auth_json)
def encode_full_header(auth):
""" Returns the given auth block encoded for the X-Registry-Config header.
"""
return encode_header({'configs': auth})
def load_config(root=None):
"""Loads authentication data from a Docker configuration file in the given
root directory."""
conf = {}
data = None
config_file = os.path.join(root or os.environ.get('HOME', '.'),
DOCKER_CONFIG_FILENAME)
# First try as JSON
try:
with open(config_file) as f:
conf = {}
for registry, entry in six.iteritems(json.load(f)):
username, password = decode_auth(entry['auth'])
conf[registry] = {
'username': username,
'password': password,
'email': entry['email'],
'serveraddress': registry,
}
return conf
except:
pass
# If that fails, we assume the configuration file contains a single
# authentication token for the public registry in the following format:
#
# auth = AUTH_TOKEN
# email = email@domain.com
try:
data = []
for line in fileinput.input(config_file):
data.append(line.strip().split(' = ')[1])
if len(data) < 2:
# Not enough data
raise errors.InvalidConfigFile(
'Invalid or empty configuration file!')
username, password = decode_auth(data[0])
conf[INDEX_URL] = {
'username': username,
'password': password,
'email': data[1],
'serveraddress': INDEX_URL,
}
return conf
except:
pass
# If all fails, return an empty config
return {}

View File

@@ -1,769 +0,0 @@
# Copyright 2013 dotCloud inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import re
import shlex
import struct
import requests
import requests.exceptions
from fig.packages import six
from .auth import auth
from .unixconn import unixconn
from .utils import utils
from . import errors
if not six.PY3:
import websocket
DEFAULT_DOCKER_API_VERSION = '1.9'
DEFAULT_TIMEOUT_SECONDS = 60
STREAM_HEADER_SIZE_BYTES = 8
class Client(requests.Session):
def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION,
timeout=DEFAULT_TIMEOUT_SECONDS):
super(Client, self).__init__()
if base_url is None:
base_url = "http+unix://var/run/docker.sock"
if 'unix:///' in base_url:
base_url = base_url.replace('unix:/', 'unix:')
if base_url.startswith('unix:'):
base_url = "http+" + base_url
if base_url.startswith('tcp:'):
base_url = base_url.replace('tcp:', 'http:')
if base_url.endswith('/'):
base_url = base_url[:-1]
self.base_url = base_url
self._version = version
self._timeout = timeout
self._auth_configs = auth.load_config()
self.mount('http+unix://', unixconn.UnixAdapter(base_url, timeout))
def _set_request_timeout(self, kwargs):
"""Prepare the kwargs for an HTTP request by inserting the timeout
parameter, if not already present."""
kwargs.setdefault('timeout', self._timeout)
return kwargs
def _post(self, url, **kwargs):
return self.post(url, **self._set_request_timeout(kwargs))
def _get(self, url, **kwargs):
return self.get(url, **self._set_request_timeout(kwargs))
def _delete(self, url, **kwargs):
return self.delete(url, **self._set_request_timeout(kwargs))
def _url(self, path):
return '{0}/v{1}{2}'.format(self.base_url, self._version, path)
def _raise_for_status(self, response, explanation=None):
"""Raises stored :class:`APIError`, if one occurred."""
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
raise errors.APIError(e, response, explanation=explanation)
def _result(self, response, json=False, binary=False):
assert not (json and binary)
self._raise_for_status(response)
if json:
return response.json()
if binary:
return response.content
return response.text
def _container_config(self, image, command, hostname=None, user=None,
detach=False, stdin_open=False, tty=False,
mem_limit=0, ports=None, environment=None, dns=None,
volumes=None, volumes_from=None,
network_disabled=False, entrypoint=None,
cpu_shares=None, working_dir=None, domainname=None):
if isinstance(command, six.string_types):
command = shlex.split(str(command))
if isinstance(environment, dict):
environment = [
'{0}={1}'.format(k, v) for k, v in environment.items()
]
if isinstance(ports, list):
exposed_ports = {}
for port_definition in ports:
port = port_definition
proto = 'tcp'
if isinstance(port_definition, tuple):
if len(port_definition) == 2:
proto = port_definition[1]
port = port_definition[0]
exposed_ports['{0}/{1}'.format(port, proto)] = {}
ports = exposed_ports
if isinstance(volumes, list):
volumes_dict = {}
for vol in volumes:
volumes_dict[vol] = {}
volumes = volumes_dict
if volumes_from and not isinstance(volumes_from, six.string_types):
volumes_from = ','.join(volumes_from)
attach_stdin = False
attach_stdout = False
attach_stderr = False
stdin_once = False
if not detach:
attach_stdout = True
attach_stderr = True
if stdin_open:
attach_stdin = True
stdin_once = True
return {
'Hostname': hostname,
'Domainname': domainname,
'ExposedPorts': ports,
'User': user,
'Tty': tty,
'OpenStdin': stdin_open,
'StdinOnce': stdin_once,
'Memory': mem_limit,
'AttachStdin': attach_stdin,
'AttachStdout': attach_stdout,
'AttachStderr': attach_stderr,
'Env': environment,
'Cmd': command,
'Dns': dns,
'Image': image,
'Volumes': volumes,
'VolumesFrom': volumes_from,
'NetworkDisabled': network_disabled,
'Entrypoint': entrypoint,
'CpuShares': cpu_shares,
'WorkingDir': working_dir
}
def _post_json(self, url, data, **kwargs):
# Go <1.1 can't unserialize null to a string
# so we do this disgusting thing here.
data2 = {}
if data is not None:
for k, v in six.iteritems(data):
if v is not None:
data2[k] = v
if 'headers' not in kwargs:
kwargs['headers'] = {}
kwargs['headers']['Content-Type'] = 'application/json'
return self._post(url, data=json.dumps(data2), **kwargs)
def _attach_params(self, override=None):
return override or {
'stdout': 1,
'stderr': 1,
'stream': 1
}
def _attach_websocket(self, container, params=None):
if six.PY3:
raise NotImplementedError("This method is not currently supported "
"under python 3")
url = self._url("/containers/{0}/attach/ws".format(container))
req = requests.Request("POST", url, params=self._attach_params(params))
full_url = req.prepare().url
full_url = full_url.replace("http://", "ws://", 1)
full_url = full_url.replace("https://", "wss://", 1)
return self._create_websocket_connection(full_url)
def _create_websocket_connection(self, url):
return websocket.create_connection(url)
def _get_raw_response_socket(self, response):
self._raise_for_status(response)
if six.PY3:
return response.raw._fp.fp.raw._sock
else:
return response.raw._fp.fp._sock
def _stream_helper(self, response):
"""Generator for data coming from a chunked-encoded HTTP response."""
socket_fp = self._get_raw_response_socket(response)
socket_fp.setblocking(1)
socket = socket_fp.makefile()
while True:
# Because Docker introduced newlines at the end of chunks in v0.9,
# and only on some API endpoints, we have to cater for both cases.
size_line = socket.readline()
if size_line == '\r\n':
size_line = socket.readline()
size = int(size_line, 16)
if size <= 0:
break
data = socket.readline()
if not data:
break
yield data
def _multiplexed_buffer_helper(self, response):
"""A generator of multiplexed data blocks read from a buffered
response."""
buf = self._result(response, binary=True)
walker = 0
while True:
if len(buf[walker:]) < 8:
break
_, length = struct.unpack_from('>BxxxL', buf[walker:])
start = walker + STREAM_HEADER_SIZE_BYTES
end = start + length
walker = end
yield str(buf[start:end])
def _multiplexed_socket_stream_helper(self, response):
"""A generator of multiplexed data blocks coming from a response
socket."""
socket = self._get_raw_response_socket(response)
def recvall(socket, size):
blocks = []
while size > 0:
block = socket.recv(size)
if not block:
return None
blocks.append(block)
size -= len(block)
sep = bytes() if six.PY3 else str()
data = sep.join(blocks)
return data
while True:
socket.settimeout(None)
header = recvall(socket, STREAM_HEADER_SIZE_BYTES)
if not header:
break
_, length = struct.unpack('>BxxxL', header)
if not length:
break
data = recvall(socket, length)
if not data:
break
yield data
def attach(self, container, stdout=True, stderr=True,
stream=False, logs=False):
if isinstance(container, dict):
container = container.get('Id')
params = {
'logs': logs and 1 or 0,
'stdout': stdout and 1 or 0,
'stderr': stderr and 1 or 0,
'stream': stream and 1 or 0,
}
u = self._url("/containers/{0}/attach".format(container))
response = self._post(u, params=params, stream=stream)
# Stream multi-plexing was only introduced in API v1.6. Anything before
# that needs old-style streaming.
if utils.compare_version('1.6', self._version) < 0:
def stream_result():
self._raise_for_status(response)
for line in response.iter_lines(chunk_size=1,
decode_unicode=True):
# filter out keep-alive new lines
if line:
yield line
return stream_result() if stream else \
self._result(response, binary=True)
return stream and self._multiplexed_socket_stream_helper(response) or \
''.join([x for x in self._multiplexed_buffer_helper(response)])
def attach_socket(self, container, params=None, ws=False):
if params is None:
params = {
'stdout': 1,
'stderr': 1,
'stream': 1
}
if ws:
return self._attach_websocket(container, params)
if isinstance(container, dict):
container = container.get('Id')
u = self._url("/containers/{0}/attach".format(container))
return self._get_raw_response_socket(self.post(
u, None, params=self._attach_params(params), stream=True))
def build(self, path=None, tag=None, quiet=False, fileobj=None,
nocache=False, rm=False, stream=False, timeout=None):
remote = context = headers = None
if path is None and fileobj is None:
raise TypeError("Either path or fileobj needs to be provided.")
if fileobj is not None:
context = utils.mkbuildcontext(fileobj)
elif path.startswith(('http://', 'https://', 'git://', 'github.com/')):
remote = path
else:
context = utils.tar(path)
if utils.compare_version('1.8', self._version) >= 0:
stream = True
u = self._url('/build')
params = {
't': tag,
'remote': remote,
'q': quiet,
'nocache': nocache,
'rm': rm
}
if context is not None:
headers = {'Content-Type': 'application/tar'}
if utils.compare_version('1.9', self._version) >= 0:
# If we don't have any auth data so far, try reloading the config
# file one more time in case anything showed up in there.
if not self._auth_configs:
self._auth_configs = auth.load_config()
# Send the full auth configuration (if any exists), since the build
# could use any (or all) of the registries.
if self._auth_configs:
headers['X-Registry-Config'] = auth.encode_full_header(
self._auth_configs
)
response = self._post(
u,
data=context,
params=params,
headers=headers,
stream=stream,
timeout=timeout,
)
if context is not None:
context.close()
if stream:
return self._stream_helper(response)
else:
output = self._result(response)
srch = r'Successfully built ([0-9a-f]+)'
match = re.search(srch, output)
if not match:
return None, output
return match.group(1), output
def commit(self, container, repository=None, tag=None, message=None,
author=None, conf=None):
params = {
'container': container,
'repo': repository,
'tag': tag,
'comment': message,
'author': author
}
u = self._url("/commit")
return self._result(self._post_json(u, data=conf, params=params),
json=True)
def containers(self, quiet=False, all=False, trunc=True, latest=False,
since=None, before=None, limit=-1):
params = {
'limit': 1 if latest else limit,
'all': 1 if all else 0,
'trunc_cmd': 1 if trunc else 0,
'since': since,
'before': before
}
u = self._url("/containers/json")
res = self._result(self._get(u, params=params), True)
if quiet:
return [{'Id': x['Id']} for x in res]
return res
def copy(self, container, resource):
if isinstance(container, dict):
container = container.get('Id')
res = self._post_json(
self._url("/containers/{0}/copy".format(container)),
data={"Resource": resource},
stream=True
)
self._raise_for_status(res)
return res.raw
def create_container(self, image, command=None, hostname=None, user=None,
detach=False, stdin_open=False, tty=False,
mem_limit=0, ports=None, environment=None, dns=None,
volumes=None, volumes_from=None,
network_disabled=False, name=None, entrypoint=None,
cpu_shares=None, working_dir=None, domainname=None):
config = self._container_config(
image, command, hostname, user, detach, stdin_open, tty, mem_limit,
ports, environment, dns, volumes, volumes_from, network_disabled,
entrypoint, cpu_shares, working_dir, domainname
)
return self.create_container_from_config(config, name)
def create_container_from_config(self, config, name=None):
u = self._url("/containers/create")
params = {
'name': name
}
res = self._post_json(u, data=config, params=params)
return self._result(res, True)
def diff(self, container):
if isinstance(container, dict):
container = container.get('Id')
return self._result(self._get(self._url("/containers/{0}/changes".
format(container))), True)
def events(self):
return self._stream_helper(self.get(self._url('/events'), stream=True))
def export(self, container):
if isinstance(container, dict):
container = container.get('Id')
res = self._get(self._url("/containers/{0}/export".format(container)),
stream=True)
self._raise_for_status(res)
return res.raw
def history(self, image):
res = self._get(self._url("/images/{0}/history".format(image)))
self._raise_for_status(res)
return self._result(res)
def images(self, name=None, quiet=False, all=False, viz=False):
if viz:
if utils.compare_version('1.7', self._version) >= 0:
raise Exception('Viz output is not supported in API >= 1.7!')
return self._result(self._get(self._url("images/viz")))
params = {
'filter': name,
'only_ids': 1 if quiet else 0,
'all': 1 if all else 0,
}
res = self._result(self._get(self._url("/images/json"), params=params),
True)
if quiet:
return [x['Id'] for x in res]
return res
def import_image(self, src=None, repository=None, tag=None, image=None):
u = self._url("/images/create")
params = {
'repo': repository,
'tag': tag
}
if src:
try:
# XXX: this is ways not optimal but the only way
# for now to import tarballs through the API
fic = open(src)
data = fic.read()
fic.close()
src = "-"
except IOError:
# file does not exists or not a file (URL)
data = None
if isinstance(src, six.string_types):
params['fromSrc'] = src
return self._result(self._post(u, data=data, params=params))
return self._result(self._post(u, data=src, params=params))
if image:
params['fromImage'] = image
return self._result(self._post(u, data=None, params=params))
raise Exception("Must specify a src or image")
def info(self):
return self._result(self._get(self._url("/info")),
True)
def insert(self, image, url, path):
api_url = self._url("/images/" + image + "/insert")
params = {
'url': url,
'path': path
}
return self._result(self._post(api_url, params=params))
def inspect_container(self, container):
if isinstance(container, dict):
container = container.get('Id')
return self._result(
self._get(self._url("/containers/{0}/json".format(container))),
True)
def inspect_image(self, image_id):
return self._result(
self._get(self._url("/images/{0}/json".format(image_id))),
True
)
def kill(self, container, signal=None):
if isinstance(container, dict):
container = container.get('Id')
url = self._url("/containers/{0}/kill".format(container))
params = {}
if signal is not None:
params['signal'] = signal
res = self._post(url, params=params)
self._raise_for_status(res)
def login(self, username, password=None, email=None, registry=None,
reauth=False):
# If we don't have any auth data so far, try reloading the config file
# one more time in case anything showed up in there.
if not self._auth_configs:
self._auth_configs = auth.load_config()
registry = registry or auth.INDEX_URL
authcfg = auth.resolve_authconfig(self._auth_configs, registry)
# If we found an existing auth config for this registry and username
# combination, we can return it immediately unless reauth is requested.
if authcfg and authcfg.get('username', None) == username \
and not reauth:
return authcfg
req_data = {
'username': username,
'password': password,
'email': email,
'serveraddress': registry,
}
response = self._post_json(self._url('/auth'), data=req_data)
if response.status_code == 200:
self._auth_configs[registry] = req_data
return self._result(response, json=True)
def logs(self, container, stdout=True, stderr=True, stream=False):
return self.attach(
container,
stdout=stdout,
stderr=stderr,
stream=stream,
logs=True
)
def port(self, container, private_port):
if isinstance(container, dict):
container = container.get('Id')
res = self._get(self._url("/containers/{0}/json".format(container)))
self._raise_for_status(res)
json_ = res.json()
s_port = str(private_port)
h_ports = None
h_ports = json_['NetworkSettings']['Ports'].get(s_port + '/udp')
if h_ports is None:
h_ports = json_['NetworkSettings']['Ports'].get(s_port + '/tcp')
return h_ports
def pull(self, repository, tag=None, stream=False):
registry, repo_name = auth.resolve_repository_name(repository)
if repo_name.count(":") == 1:
repository, tag = repository.rsplit(":", 1)
params = {
'tag': tag,
'fromImage': repository
}
headers = {}
if utils.compare_version('1.5', self._version) >= 0:
# If we don't have any auth data so far, try reloading the config
# file one more time in case anything showed up in there.
if not self._auth_configs:
self._auth_configs = auth.load_config()
authcfg = auth.resolve_authconfig(self._auth_configs, registry)
# Do not fail here if no authentication exists for this specific
# registry as we can have a readonly pull. Just put the header if
# we can.
if authcfg:
headers['X-Registry-Auth'] = auth.encode_header(authcfg)
response = self._post(self._url('/images/create'), params=params,
headers=headers, stream=stream, timeout=None)
if stream:
return self._stream_helper(response)
else:
return self._result(response)
def push(self, repository, stream=False):
registry, repo_name = auth.resolve_repository_name(repository)
u = self._url("/images/{0}/push".format(repository))
headers = {}
if utils.compare_version('1.5', self._version) >= 0:
# If we don't have any auth data so far, try reloading the config
# file one more time in case anything showed up in there.
if not self._auth_configs:
self._auth_configs = auth.load_config()
authcfg = auth.resolve_authconfig(self._auth_configs, registry)
# Do not fail here if no authentication exists for this specific
# registry as we can have a readonly pull. Just put the header if
# we can.
if authcfg:
headers['X-Registry-Auth'] = auth.encode_header(authcfg)
response = self._post_json(u, None, headers=headers, stream=stream)
else:
response = self._post_json(u, None, stream=stream)
return stream and self._stream_helper(response) \
or self._result(response)
def remove_container(self, container, v=False, link=False):
if isinstance(container, dict):
container = container.get('Id')
params = {'v': v, 'link': link}
res = self._delete(self._url("/containers/" + container),
params=params)
self._raise_for_status(res)
def remove_image(self, image):
res = self._delete(self._url("/images/" + image))
self._raise_for_status(res)
def restart(self, container, timeout=10):
if isinstance(container, dict):
container = container.get('Id')
params = {'t': timeout}
url = self._url("/containers/{0}/restart".format(container))
res = self._post(url, params=params)
self._raise_for_status(res)
def search(self, term):
return self._result(self._get(self._url("/images/search"),
params={'term': term}),
True)
def start(self, container, binds=None, volumes_from=None, port_bindings=None,
lxc_conf=None, publish_all_ports=False, links=None, privileged=False):
if isinstance(container, dict):
container = container.get('Id')
if isinstance(lxc_conf, dict):
formatted = []
for k, v in six.iteritems(lxc_conf):
formatted.append({'Key': k, 'Value': str(v)})
lxc_conf = formatted
start_config = {
'LxcConf': lxc_conf
}
if binds:
bind_pairs = [
'%s:%s:%s' % (
h, d['bind'],
'ro' if 'ro' in d and d['ro'] else 'rw'
) for h, d in binds.items()
]
start_config['Binds'] = bind_pairs
if volumes_from and not isinstance(volumes_from, six.string_types):
volumes_from = ','.join(volumes_from)
start_config['VolumesFrom'] = volumes_from
if port_bindings:
start_config['PortBindings'] = utils.convert_port_bindings(
port_bindings
)
start_config['PublishAllPorts'] = publish_all_ports
if links:
if isinstance(links, dict):
links = six.iteritems(links)
formatted_links = [
'{0}:{1}'.format(k, v) for k, v in sorted(links)
]
start_config['Links'] = formatted_links
start_config['Privileged'] = privileged
url = self._url("/containers/{0}/start".format(container))
res = self._post_json(url, data=start_config)
self._raise_for_status(res)
def stop(self, container, timeout=10):
if isinstance(container, dict):
container = container.get('Id')
params = {'t': timeout}
url = self._url("/containers/{0}/stop".format(container))
res = self._post(url, params=params,
timeout=max(timeout, self._timeout))
self._raise_for_status(res)
def tag(self, image, repository, tag=None, force=False):
params = {
'tag': tag,
'repo': repository,
'force': 1 if force else 0
}
url = self._url("/images/{0}/tag".format(image))
res = self._post(url, params=params)
self._raise_for_status(res)
return res.status_code == 201
def top(self, container):
u = self._url("/containers/{0}/top".format(container))
return self._result(self._get(u), True)
def version(self):
return self._result(self._get(self._url("/version")), True)
def wait(self, container):
if isinstance(container, dict):
container = container.get('Id')
url = self._url("/containers/{0}/wait".format(container))
res = self._post(url, timeout=None)
self._raise_for_status(res)
json_ = res.json()
if 'StatusCode' in json_:
return json_['StatusCode']
return -1

View File

@@ -1,61 +0,0 @@
# Copyright 2014 dotCloud inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import requests
class APIError(requests.exceptions.HTTPError):
def __init__(self, message, response, explanation=None):
# requests 1.2 supports response as a keyword argument, but
# requests 1.1 doesn't
super(APIError, self).__init__(message)
self.response = response
self.explanation = explanation
if self.explanation is None and response.content:
self.explanation = response.content.strip()
def __str__(self):
message = super(APIError, self).__str__()
if self.is_client_error():
message = '%s Client Error: %s' % (
self.response.status_code, self.response.reason)
elif self.is_server_error():
message = '%s Server Error: %s' % (
self.response.status_code, self.response.reason)
if self.explanation:
message = '%s ("%s")' % (message, self.explanation)
return message
def is_client_error(self):
return 400 <= self.response.status_code < 500
def is_server_error(self):
return 500 <= self.response.status_code < 600
class DockerException(Exception):
pass
class InvalidRepository(DockerException):
pass
class InvalidConfigFile(DockerException):
pass

View File

@@ -1 +0,0 @@
from .unixconn import UnixAdapter # flake8: noqa

View File

@@ -1,71 +0,0 @@
# Copyright 2013 dotCloud inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from fig.packages import six
if six.PY3:
import http.client as httplib
else:
import httplib
import requests.adapters
import socket
try:
import requests.packages.urllib3.connectionpool as connectionpool
except ImportError:
import urllib3.connectionpool as connectionpool
class UnixHTTPConnection(httplib.HTTPConnection, object):
def __init__(self, base_url, unix_socket, timeout=60):
httplib.HTTPConnection.__init__(self, 'localhost', timeout=timeout)
self.base_url = base_url
self.unix_socket = unix_socket
self.timeout = timeout
def connect(self):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
sock.connect(self.base_url.replace("http+unix:/", ""))
self.sock = sock
def _extract_path(self, url):
# remove the base_url entirely..
return url.replace(self.base_url, "")
def request(self, method, url, **kwargs):
url = self._extract_path(self.unix_socket)
super(UnixHTTPConnection, self).request(method, url, **kwargs)
class UnixHTTPConnectionPool(connectionpool.HTTPConnectionPool):
def __init__(self, base_url, socket_path, timeout=60):
connectionpool.HTTPConnectionPool.__init__(self, 'localhost',
timeout=timeout)
self.base_url = base_url
self.socket_path = socket_path
self.timeout = timeout
def _new_conn(self):
return UnixHTTPConnection(self.base_url, self.socket_path,
self.timeout)
class UnixAdapter(requests.adapters.HTTPAdapter):
def __init__(self, base_url, timeout=60):
self.base_url = base_url
self.timeout = timeout
super(UnixAdapter, self).__init__()
def get_connection(self, socket_path, proxies=None):
return UnixHTTPConnectionPool(self.base_url, socket_path, self.timeout)

View File

@@ -1,3 +0,0 @@
from .utils import (
compare_version, convert_port_bindings, mkbuildcontext, ping, tar, parse_repository_tag
) # flake8: noqa

View File

@@ -1,128 +0,0 @@
# Copyright 2013 dotCloud inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import io
import tarfile
import tempfile
from distutils.version import StrictVersion
import requests
from fig.packages import six
def mkbuildcontext(dockerfile):
f = tempfile.NamedTemporaryFile()
t = tarfile.open(mode='w', fileobj=f)
if isinstance(dockerfile, io.StringIO):
dfinfo = tarfile.TarInfo('Dockerfile')
if six.PY3:
raise TypeError('Please use io.BytesIO to create in-memory '
'Dockerfiles with Python 3')
else:
dfinfo.size = len(dockerfile.getvalue())
elif isinstance(dockerfile, io.BytesIO):
dfinfo = tarfile.TarInfo('Dockerfile')
dfinfo.size = len(dockerfile.getvalue())
else:
dfinfo = t.gettarinfo(fileobj=dockerfile, arcname='Dockerfile')
t.addfile(dfinfo, dockerfile)
t.close()
f.seek(0)
return f
def tar(path):
f = tempfile.NamedTemporaryFile()
t = tarfile.open(mode='w', fileobj=f)
t.add(path, arcname='.')
t.close()
f.seek(0)
return f
def compare_version(v1, v2):
"""Compare docker versions
>>> v1 = '1.9'
>>> v2 = '1.10'
>>> compare_version(v1, v2)
1
>>> compare_version(v2, v1)
-1
>>> compare_version(v2, v2)
0
"""
s1 = StrictVersion(v1)
s2 = StrictVersion(v2)
if s1 == s2:
return 0
elif s1 > s2:
return -1
else:
return 1
def ping(url):
try:
res = requests.get(url)
except Exception:
return False
else:
return res.status_code < 400
def _convert_port_binding(binding):
result = {'HostIp': '', 'HostPort': ''}
if isinstance(binding, tuple):
if len(binding) == 2:
result['HostPort'] = binding[1]
result['HostIp'] = binding[0]
elif isinstance(binding[0], six.string_types):
result['HostIp'] = binding[0]
else:
result['HostPort'] = binding[0]
else:
result['HostPort'] = binding
if result['HostPort'] is None:
result['HostPort'] = ''
else:
result['HostPort'] = str(result['HostPort'])
return result
def convert_port_bindings(port_bindings):
result = {}
for k, v in six.iteritems(port_bindings):
key = str(k)
if '/' not in key:
key = key + '/tcp'
if isinstance(v, list):
result[key] = [_convert_port_binding(binding) for binding in v]
else:
result[key] = [_convert_port_binding(v)]
return result
def parse_repository_tag(repo):
column_index = repo.rfind(':')
if column_index < 0:
return repo, ""
tag = repo[column_index+1:]
slash_index = tag.find('/')
if slash_index < 0:
return repo[:column_index], tag
return repo, ""

View File

@@ -1,404 +0,0 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# Copyright (c) 2010-2013 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.3.0"
# True if we are running on Python 3.
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result)
# This is a bit ugly, but it avoids running this again.
delattr(tp, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
del attr
moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
_iterlists = "lists"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
_iterlists = "iterlists"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
def iterkeys(d, **kw):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)(**kw))
def itervalues(d, **kw):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)(**kw))
def iteritems(d, **kw):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)(**kw))
def iterlists(d, **kw):
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
return iter(getattr(d, _iterlists)(**kw))
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
int2byte = chr
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
import builtins
exec_ = getattr(builtins, "exec")
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
print_ = getattr(builtins, "print")
del builtins
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
def print_(*args, **kwargs):
"""The new-style print function."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
def with_metaclass(meta, base=object):
"""Create a base class with a metaclass."""
return meta("NewBase", (base,), {})

View File

@@ -1,166 +0,0 @@
from __future__ import unicode_literals
from __future__ import absolute_import
import logging
from .service import Service
log = logging.getLogger(__name__)
def sort_service_dicts(services):
# Topological sort (Cormen/Tarjan algorithm).
unmarked = services[:]
temporary_marked = set()
sorted_services = []
get_service_names = lambda links: [link.split(':')[0] for link in links]
def visit(n):
if n['name'] in temporary_marked:
if n['name'] in get_service_names(n.get('links', [])):
raise DependencyError('A service can not link to itself: %s' % n['name'])
else:
raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
if n in unmarked:
temporary_marked.add(n['name'])
dependents = [m for m in services if n['name'] in get_service_names(m.get('links', []))]
for m in dependents:
visit(m)
temporary_marked.remove(n['name'])
unmarked.remove(n)
sorted_services.insert(0, n)
while unmarked:
visit(unmarked[-1])
return sorted_services
class Project(object):
"""
A collection of services.
"""
def __init__(self, name, services, client):
self.name = name
self.services = services
self.client = client
@classmethod
def from_dicts(cls, name, service_dicts, client):
"""
Construct a ServiceCollection from a list of dicts representing services.
"""
project = cls(name, [], client)
for service_dict in sort_service_dicts(service_dicts):
# Reference links by object
links = []
if 'links' in service_dict:
for link in service_dict.get('links', []):
if ':' in link:
service_name, link_name = link.split(':', 1)
else:
service_name, link_name = link, None
try:
links.append((project.get_service(service_name), link_name))
except NoSuchService:
raise ConfigurationError('Service "%s" has a link to service "%s" which does not exist.' % (service_dict['name'], service_name))
del service_dict['links']
project.services.append(Service(client=client, project=name, links=links, **service_dict))
return project
@classmethod
def from_config(cls, name, config, client):
dicts = []
for service_name, service in list(config.items()):
if not isinstance(service, dict):
raise ConfigurationError('Service "%s" doesn\'t have any configuration options. All top level keys in your fig.yml must map to a dictionary of configuration options.')
service['name'] = service_name
dicts.append(service)
return cls.from_dicts(name, dicts, client)
def get_service(self, name):
"""
Retrieve a service by name. Raises NoSuchService
if the named service does not exist.
"""
for service in self.services:
if service.name == name:
return service
raise NoSuchService(name)
def get_services(self, service_names=None):
"""
Returns a list of this project's services filtered
by the provided list of names, or all services if
service_names is None or [].
Preserves the original order of self.services.
Raises NoSuchService if any of the named services
do not exist.
"""
if service_names is None or len(service_names) == 0:
return self.services
else:
unsorted = [self.get_service(name) for name in service_names]
return [s for s in self.services if s in unsorted]
def start(self, service_names=None, **options):
for service in self.get_services(service_names):
service.start(**options)
def stop(self, service_names=None, **options):
for service in reversed(self.get_services(service_names)):
service.stop(**options)
def kill(self, service_names=None, **options):
for service in reversed(self.get_services(service_names)):
service.kill(**options)
def build(self, service_names=None, **options):
for service in self.get_services(service_names):
if service.can_be_built():
service.build(**options)
else:
log.info('%s uses an image, skipping' % service.name)
def up(self, service_names=None):
new_containers = []
for service in self.get_services(service_names):
for (_, new) in service.recreate_containers():
new_containers.append(new)
return new_containers
def remove_stopped(self, service_names=None, **options):
for service in self.get_services(service_names):
service.remove_stopped(**options)
def containers(self, service_names=None, *args, **kwargs):
l = []
for service in self.get_services(service_names):
for container in service.containers(*args, **kwargs):
l.append(container)
return l
class NoSuchService(Exception):
def __init__(self, name):
self.name = name
self.msg = "No such service: %s" % self.name
def __str__(self):
return self.msg
class ConfigurationError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class DependencyError(ConfigurationError):
pass

View File

@@ -1,385 +0,0 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from .packages.docker.errors import APIError
import logging
import re
import os
import sys
from .container import Container
from .progress_stream import stream_output, StreamOutputError
log = logging.getLogger(__name__)
DOCKER_CONFIG_KEYS = ['image', 'command', 'hostname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'volumes_from', 'entrypoint', 'privileged']
DOCKER_CONFIG_HINTS = {
'link' : 'links',
'port' : 'ports',
'privilege' : 'privileged',
'priviliged': 'privileged',
'privilige' : 'privileged',
'volume' : 'volumes',
}
class BuildError(Exception):
def __init__(self, service, reason):
self.service = service
self.reason = reason
class CannotBeScaledError(Exception):
pass
class ConfigError(ValueError):
pass
class Service(object):
def __init__(self, name, client=None, project='default', links=[], **options):
if not re.match('^[a-zA-Z0-9]+$', name):
raise ConfigError('Invalid name: %s' % name)
if not re.match('^[a-zA-Z0-9]+$', project):
raise ConfigError('Invalid project: %s' % project)
if 'image' in options and 'build' in options:
raise ConfigError('Service %s has both an image and build path specified. A service can either be built to image or use an existing image, not both.' % name)
supported_options = DOCKER_CONFIG_KEYS + ['build', 'expose']
for k in options:
if k not in supported_options:
msg = "Unsupported config option for %s service: '%s'" % (name, k)
if k in DOCKER_CONFIG_HINTS:
msg += " (did you mean '%s'?)" % DOCKER_CONFIG_HINTS[k]
raise ConfigError(msg)
self.name = name
self.client = client
self.project = project
self.links = links or []
self.options = options
def containers(self, stopped=False, one_off=False):
l = []
for container in self.client.containers(all=stopped):
name = get_container_name(container)
if not name or not is_valid_name(name, one_off):
continue
project, name, number = parse_name(name)
if project == self.project and name == self.name:
l.append(Container.from_ps(self.client, container))
return l
def start(self, **options):
for c in self.containers(stopped=True):
if not c.is_running:
log.info("Starting %s..." % c.name)
self.start_container(c, **options)
def stop(self, **options):
for c in self.containers():
log.info("Stopping %s..." % c.name)
c.stop(**options)
def kill(self, **options):
for c in self.containers():
log.info("Killing %s..." % c.name)
c.kill(**options)
def scale(self, desired_num):
"""
Adjusts the number of containers to the specified number and ensures they are running.
- creates containers until there are at least `desired_num`
- stops containers until there are at most `desired_num` running
- starts containers until there are at least `desired_num` running
- removes all stopped containers
"""
if not self.can_be_scaled():
raise CannotBeScaledError()
# Create enough containers
containers = self.containers(stopped=True)
while len(containers) < desired_num:
containers.append(self.create_container())
running_containers = []
stopped_containers = []
for c in containers:
if c.is_running:
running_containers.append(c)
else:
stopped_containers.append(c)
running_containers.sort(key=lambda c: c.number)
stopped_containers.sort(key=lambda c: c.number)
# Stop containers
while len(running_containers) > desired_num:
c = running_containers.pop()
log.info("Stopping %s..." % c.name)
c.stop(timeout=1)
stopped_containers.append(c)
# Start containers
while len(running_containers) < desired_num:
c = stopped_containers.pop(0)
log.info("Starting %s..." % c.name)
self.start_container(c)
running_containers.append(c)
self.remove_stopped()
def remove_stopped(self, **options):
for c in self.containers(stopped=True):
if not c.is_running:
log.info("Removing %s..." % c.name)
c.remove(**options)
def create_container(self, one_off=False, **override_options):
"""
Create a container for this service. If the image doesn't exist, attempt to pull
it.
"""
container_options = self._get_container_create_options(override_options, one_off=one_off)
try:
return Container.create(self.client, **container_options)
except APIError as e:
if e.response.status_code == 404 and e.explanation and 'No such image' in str(e.explanation):
log.info('Pulling image %s...' % container_options['image'])
output = self.client.pull(container_options['image'], stream=True)
stream_output(output, sys.stdout)
return Container.create(self.client, **container_options)
raise
def recreate_containers(self, **override_options):
"""
If a container for this service doesn't exist, create and start one. If there are
any, stop them, create+start new ones, and remove the old containers.
"""
containers = self.containers(stopped=True)
if len(containers) == 0:
log.info("Creating %s..." % self.next_container_name())
container = self.create_container(**override_options)
self.start_container(container)
return [(None, container)]
else:
tuples = []
for c in containers:
log.info("Recreating %s..." % c.name)
tuples.append(self.recreate_container(c, **override_options))
return tuples
def recreate_container(self, container, **override_options):
if container.is_running:
container.stop(timeout=1)
intermediate_container = Container.create(
self.client,
image=container.image,
volumes_from=container.id,
entrypoint=['echo'],
command=[],
)
intermediate_container.start(volumes_from=container.id)
intermediate_container.wait()
container.remove()
options = dict(override_options)
options['volumes_from'] = intermediate_container.id
new_container = self.create_container(**options)
self.start_container(new_container, volumes_from=intermediate_container.id)
intermediate_container.remove()
return (intermediate_container, new_container)
def start_container(self, container=None, volumes_from=None, **override_options):
if container is None:
container = self.create_container(**override_options)
options = self.options.copy()
options.update(override_options)
port_bindings = {}
if options.get('ports', None) is not None:
for port in options['ports']:
port = str(port)
if ':' in port:
external_port, internal_port = port.split(':', 1)
else:
external_port, internal_port = (None, port)
port_bindings[internal_port] = external_port
volume_bindings = {}
if options.get('volumes', None) is not None:
for volume in options['volumes']:
if ':' in volume:
external_dir, internal_dir = volume.split(':')
volume_bindings[os.path.abspath(external_dir)] = {
'bind': internal_dir,
'ro': False,
}
privileged = options.get('privileged', False)
container.start(
links=self._get_links(link_to_self=override_options.get('one_off', False)),
port_bindings=port_bindings,
binds=volume_bindings,
volumes_from=volumes_from,
privileged=privileged,
)
return container
def next_container_name(self, one_off=False):
bits = [self.project, self.name]
if one_off:
bits.append('run')
return '_'.join(bits + [str(self.next_container_number(one_off=one_off))])
def next_container_number(self, one_off=False):
numbers = [parse_name(c.name)[2] for c in self.containers(stopped=True, one_off=one_off)]
if len(numbers) == 0:
return 1
else:
return max(numbers) + 1
def _get_links(self, link_to_self):
links = []
for service, link_name in self.links:
for container in service.containers():
if link_name:
links.append((container.name, link_name))
links.append((container.name, container.name))
links.append((container.name, container.name_without_project))
if link_to_self:
for container in self.containers():
links.append((container.name, container.name))
links.append((container.name, container.name_without_project))
return links
def _get_container_create_options(self, override_options, one_off=False):
container_options = dict((k, self.options[k]) for k in DOCKER_CONFIG_KEYS if k in self.options)
container_options.update(override_options)
container_options['name'] = self.next_container_name(one_off)
if 'ports' in container_options or 'expose' in self.options:
ports = []
all_ports = container_options.get('ports', []) + self.options.get('expose', [])
for port in all_ports:
port = str(port)
if ':' in port:
port = port.split(':')[-1]
if '/' in port:
port = tuple(port.split('/'))
ports.append(port)
container_options['ports'] = ports
if 'volumes' in container_options:
container_options['volumes'] = dict((split_volume(v)[1], {}) for v in container_options['volumes'])
if self.can_be_built():
if len(self.client.images(name=self._build_tag_name())) == 0:
self.build()
container_options['image'] = self._build_tag_name()
# Priviliged is only required for starting containers, not for creating them
if 'privileged' in container_options:
del container_options['privileged']
return container_options
def build(self):
log.info('Building %s...' % self.name)
build_output = self.client.build(
self.options['build'],
tag=self._build_tag_name(),
stream=True,
rm=True
)
try:
all_events = stream_output(build_output, sys.stdout)
except StreamOutputError, e:
raise BuildError(self, unicode(e))
image_id = None
for event in all_events:
if 'stream' in event:
match = re.search(r'Successfully built ([0-9a-f]+)', event.get('stream', ''))
if match:
image_id = match.group(1)
if image_id is None:
raise BuildError(self)
return image_id
def can_be_built(self):
return 'build' in self.options
def _build_tag_name(self):
"""
The tag to give to images built for this service.
"""
return '%s_%s' % (self.project, self.name)
def can_be_scaled(self):
for port in self.options.get('ports', []):
if ':' in str(port):
return False
return True
NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(run_)?(\d+)$')
def is_valid_name(name, one_off=False):
match = NAME_RE.match(name)
if match is None:
return False
if one_off:
return match.group(3) == 'run_'
else:
return match.group(3) is None
def parse_name(name, one_off=False):
match = NAME_RE.match(name)
(project, service_name, _, suffix) = match.groups()
return (project, service_name, int(suffix))
def get_container_name(container):
if not container.get('Name') and not container.get('Names'):
return None
# inspect
if 'Name' in container:
return container['Name']
# ps
for name in container['Names']:
if len(name.split('/')) == 2:
return name[1:]
def split_volume(v):
"""
If v is of the format EXTERNAL:INTERNAL, returns (EXTERNAL, INTERNAL).
If v is of the format INTERNAL, returns (None, INTERNAL).
"""
if ':' in v:
return v.split(':', 1)
else:
return (None, v)

View File

@@ -1,4 +1,6 @@
mock==1.0.1
nose==1.3.0
pyinstaller==2.1
unittest2
mock >= 1.0.1
nose==1.3.4
git+https://github.com/pyinstaller/pyinstaller.git@12e40471c77f588ea5be352f7219c873ddaae056#egg=pyinstaller
unittest2==0.8.0
flake8==2.3.0
pep8==1.6.1

View File

@@ -1,5 +1,8 @@
docopt==0.6.1
PyYAML==3.10
docker-py==0.7.1
dockerpty==0.3.2
docopt==0.6.1
requests==2.2.1
six==1.7.3
texttable==0.8.1
websocket-client==0.11.0

33
script/.validate Normal file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
if [ -z "$VALIDATE_UPSTREAM" ]; then
# this is kind of an expensive check, so let's not do this twice if we
# are running more than one validate bundlescript
VALIDATE_REPO='https://github.com/docker/fig.git'
VALIDATE_BRANCH='master'
if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then
VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git"
VALIDATE_BRANCH="${TRAVIS_BRANCH}"
fi
VALIDATE_HEAD="$(git rev-parse --verify HEAD)"
git fetch -q "$VALIDATE_REPO" "refs/heads/$VALIDATE_BRANCH"
VALIDATE_UPSTREAM="$(git rev-parse --verify FETCH_HEAD)"
VALIDATE_COMMIT_LOG="$VALIDATE_UPSTREAM..$VALIDATE_HEAD"
VALIDATE_COMMIT_DIFF="$VALIDATE_UPSTREAM...$VALIDATE_HEAD"
validate_diff() {
if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then
git diff "$VALIDATE_COMMIT_DIFF" "$@"
fi
}
validate_log() {
if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then
git log "$VALIDATE_COMMIT_LOG" "$@"
fi
}
fi

View File

@@ -1,5 +0,0 @@
#!/bin/bash
set -ex
pushd docs
fig run jekyll jekyll build
popd

View File

@@ -2,6 +2,7 @@
set -ex
mkdir -p `pwd`/dist
chmod 777 `pwd`/dist
docker build -t fig .
docker run -v `pwd`/dist:/code/dist fig pyinstaller -F bin/fig
docker run -v `pwd`/dist:/code/dist fig dist/fig --version
docker build -t docker-compose .
docker run -u user -v `pwd`/dist:/code/dist --rm --entrypoint pyinstaller docker-compose -F bin/docker-compose
mv dist/docker-compose dist/docker-compose-Linux-x86_64
docker run -u user -v `pwd`/dist:/code/dist --rm --entrypoint dist/docker-compose-Linux-x86_64 docker-compose --version

View File

@@ -2,7 +2,9 @@
set -ex
rm -rf venv
virtualenv venv
venv/bin/pip install pyinstaller==2.1
venv/bin/pip install -r requirements.txt
venv/bin/pip install -r requirements-dev.txt
venv/bin/pip install .
venv/bin/pyinstaller -F bin/fig
dist/fig --version
venv/bin/pyinstaller -F bin/docker-compose
mv dist/docker-compose dist/docker-compose-Darwin-x86_64
dist/docker-compose-Darwin-x86_64 --version

View File

@@ -1,3 +1,3 @@
#!/bin/sh
find . -type f -name '*.pyc' -delete
rm -rf docs/_site build dist
rm -rf docs/_site build dist docker-compose.egg-info

View File

@@ -1,29 +0,0 @@
#!/bin/bash
set -ex
script/build-docs
pushd docs/_site
export GIT_DIR=.git-gh-pages
export GIT_WORK_TREE=.
if [ ! -d "$GIT_DIR" ]; then
git init
fi
if !(git remote | grep origin); then
git remote add origin git@github.com:orchardup/fig.git
fi
git fetch origin
git reset --soft origin/gh-pages
echo ".git-gh-pages" > .gitignore
git add -A .
git commit -m "update" || echo "didn't commit"
git push origin master:gh-pages
popd

21
script/dev Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# This is a script for running Compose inside a Docker container. It's handy for
# development.
#
# $ ln -s `pwd`/script/dev /usr/local/bin/docker-compose
# $ cd /a/compose/project
# $ docker-compose up
#
set -e
# Follow symbolic links
if [ -h "$0" ]; then
DIR=$(readlink "$0")
else
DIR=$0
fi
DIR="$(dirname "$DIR")"/..
docker build -t docker-compose $DIR
exec docker run -i -t -v /var/run/docker.sock:/var/run/docker.sock -v `pwd`:`pwd` -w `pwd` docker-compose $@

11
script/docs Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
set -ex
# import the existing docs build cmds from docker/docker
DOCSPORT=8000
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
DOCKER_DOCS_IMAGE="compose-docs$GIT_BRANCH"
DOCKER_RUN_DOCS="docker run --rm -it -e NOCACHE"
docker build -t "$DOCKER_DOCS_IMAGE" -f docs/Dockerfile .
$DOCKER_RUN_DOCS -p $DOCSPORT:8000 "$DOCKER_DOCS_IMAGE" mkdocs serve

View File

@@ -1,2 +0,0 @@
#!/bin/bash
open file://`pwd`/docs/_site/index.html

View File

@@ -1,2 +1,5 @@
#!/bin/sh
PYTHONIOENCODING=ascii nosetests $@
set -ex
docker build -t docker-compose .
docker run -v /var/run/docker.sock:/var/run/docker.sock --rm --entrypoint flake8 docker-compose compose
docker run -v /var/run/docker.sock:/var/run/docker.sock --rm --entrypoint nosetests docker-compose $@

View File

@@ -1,10 +0,0 @@
#!/bin/bash
set -ex
# Kill background processes on exit
trap 'kill -9 $(jobs -p)' SIGINT SIGTERM EXIT
export DOCKER_HOST=tcp://localhost:4243
orchard proxy -H $TRAVIS_JOB_ID $DOCKER_HOST &
sleep 2
nosetests -v

56
script/validate-dco Executable file
View File

@@ -0,0 +1,56 @@
#!/bin/bash
source "$(dirname "$BASH_SOURCE")/.validate"
adds=$(validate_diff --numstat | awk '{ s += $1 } END { print s }')
dels=$(validate_diff --numstat | awk '{ s += $2 } END { print s }')
notDocs="$(validate_diff --numstat | awk '$3 !~ /^docs\// { print $3 }')"
: ${adds:=0}
: ${dels:=0}
# "Username may only contain alphanumeric characters or dashes and cannot begin with a dash"
githubUsernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+'
# https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work
dcoPrefix='Signed-off-by:'
dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \\(github: ($githubUsernameRegex)\\))?$"
check_dco() {
grep -qE "$dcoRegex"
}
if [ $adds -eq 0 -a $dels -eq 0 ]; then
echo '0 adds, 0 deletions; nothing to validate! :)'
elif [ -z "$notDocs" -a $adds -le 1 -a $dels -le 1 ]; then
echo 'Congratulations! DCO small-patch-exception material!'
else
commits=( $(validate_log --format='format:%H%n') )
badCommits=()
for commit in "${commits[@]}"; do
if [ -z "$(git log -1 --format='format:' --name-status "$commit")" ]; then
# no content (ie, Merge commit, etc)
continue
fi
if ! git log -1 --format='format:%B' "$commit" | check_dco; then
badCommits+=( "$commit" )
fi
done
if [ ${#badCommits[@]} -eq 0 ]; then
echo "Congratulations! All commits are properly signed with the DCO!"
else
{
echo "These commits do not have a proper '$dcoPrefix' marker:"
for commit in "${badCommits[@]}"; do
echo " - $commit"
done
echo
echo 'Please amend each commit to include a properly formatted DCO marker.'
echo
echo 'Visit the following URL for information about the Docker DCO:'
echo ' https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work'
echo
} >&2
false
fi
fi

View File

@@ -3,9 +3,10 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from setuptools import setup, find_packages
import re
import os
import codecs
import os
import re
import sys
def read(*parts):
@@ -22,27 +23,44 @@ def find_version(*file_paths):
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
with open('requirements.txt') as f:
install_requires = f.read().splitlines()
with open('requirements-dev.txt') as f:
tests_require = f.read().splitlines()
install_requires = [
'docopt >= 0.6.1, < 0.7',
'PyYAML >= 3.10, < 4',
'requests >= 2.2.1, < 2.5.0',
'texttable >= 0.8.1, < 0.9',
'websocket-client >= 0.11.0, < 1.0',
'docker-py >= 0.6.0, < 0.8',
'dockerpty >= 0.3.2, < 0.4',
'six >= 1.3.0, < 2',
]
tests_require = [
'mock >= 1.0.1',
'nose',
'pyinstaller',
'flake8',
]
if sys.version_info < (2, 7):
tests_require.append('unittest2')
setup(
name='fig',
version=find_version("fig", "__init__.py"),
description='Punctual, lightweight development environments using Docker',
url='http://orchardup.github.io/fig/',
author='Orchard Laboratories Ltd.',
author_email='hello@orchardup.com',
name='docker-compose',
version=find_version("compose", "__init__.py"),
description='Multi-container orchestration for Docker',
url='https://www.docker.com/',
author='Docker, Inc.',
license='Apache License 2.0',
packages=find_packages(),
packages=find_packages(exclude=[ 'tests.*', 'tests' ]),
include_package_data=True,
test_suite='nose.collector',
install_requires=install_requires,
tests_require=tests_require,
entry_points="""
[console_scripts]
fig=fig.cli.main:main
docker-compose=compose.cli.main:main
""",
)

View File

@@ -0,0 +1,5 @@
implicit:
image: composetest_test
explicit:
image: composetest_test
command: [ "/bin/true" ]

View File

@@ -0,0 +1,3 @@
FROM busybox
VOLUME /data
CMD sleep 3000

View File

@@ -0,0 +1,2 @@
FROM busybox:latest
ENTRYPOINT echo "From prebuilt entrypoint"

View File

@@ -0,0 +1,2 @@
service:
build: tests/fixtures/dockerfile_with_entrypoint

11
tests/fixtures/env/one.env vendored Normal file
View File

@@ -0,0 +1,11 @@
# Keep the blank lines and comments in this file, please
ONE=2
TWO=1
# (thanks)
THREE=3
FOO=bar
# FOO=somethingelse

4
tests/fixtures/env/resolve.env vendored Normal file
View File

@@ -0,0 +1,4 @@
FILE_DEF=F1
FILE_DEF_EMPTY=
ENV_DEF
NO_DEF

2
tests/fixtures/env/two.env vendored Normal file
View File

@@ -0,0 +1,2 @@
FOO=baz
DOO=dah

View File

@@ -0,0 +1,7 @@
service:
image: busybox:latest
command: sleep 5
environment:
foo: bar
hello: world

View File

@@ -0,0 +1,11 @@
db:
image: busybox:latest
command: /bin/sleep 300
web:
image: busybox:latest
command: /bin/sleep 300
links:
- db:db
console:
image: busybox:latest
command: /bin/sleep 300

View File

@@ -0,0 +1,7 @@
simple:
image: busybox:latest
command: /bin/sleep 300
ports:
- '3000'
- '9999:3001'

View File

@@ -0,0 +1,6 @@
simple:
image: busybox:latest
command: /bin/sleep 300
another:
image: busybox:latest
command: /bin/sleep 300

View File

@@ -0,0 +1,2 @@
simple:
build: tests/fixtures/simple-dockerfile

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