Compare commits

...

49 Commits
0.4.0 ... 0.4.2

Author SHA1 Message Date
Aanand Prasad
034b66fedb Merge pull request #256 from orchardup/ship-0.4.2
Ship 0.4.2
2014-06-18 16:45:21 +01:00
Ben Firshman
eed274c632 Ship 0.4.2 2014-06-18 16:32:23 +01:00
Ben Firshman
5b10c4811f Merge pull request #255 from orchardup/fix-unicode
Fix encoding errors
2014-06-18 16:00:43 +01:00
Tobias Bradtke
2bd6e3d0a5 Do not encode chunk, just write as is. 2014-06-18 15:31:45 +01:00
Aanand Prasad
d0b5bcf26a Pass byte strings straight through LogPrinter 2014-06-18 14:51:13 +01:00
Aanand Prasad
262248d8a6 Firm up tests for split_buffer 2014-06-18 14:51:13 +01:00
Aanand Prasad
9eb3697b40 Encode all progress stream output as UTF-8
Closes #231.
2014-06-18 14:51:13 +01:00
Aanand Prasad
c246897af1 Pass script/test arguments through to nosetests 2014-06-18 14:51:13 +01:00
Aanand Prasad
cfcabce593 Extract stream_output to module 2014-06-18 14:51:12 +01:00
Aanand Prasad
e517061010 Add /venv to .gitignore 2014-06-18 14:51:12 +01:00
Aanand Prasad
feb8ad7b4c Update Dockerfile reference/tutorial links 2014-06-16 23:32:50 +01:00
Aanand Prasad
e953a32a82 Merge pull request #248 from orchardup/docker-1.0.0
Update to docs to Docker 1.0.0
2014-06-11 12:13:09 -07:00
Ben Firshman
f1390b3cb6 Merge pull request #249 from d11wtq/performance/busybox_fixtures
Use busybox in fixtures, instead of ubuntu
2014-06-11 19:59:21 +01:00
d11wtq
3a342fb25d Use busybox in fixtures, instead of ubuntu
Signed-off-by: d11wtq <chris@w3style.co.uk>
2014-06-11 10:25:50 +00:00
Ben Firshman
da80eca28c Update to docs to Docker 1.0.0
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-06-10 14:58:06 -07:00
Ben Firshman
1b5335f409 Add developer certificate of origin docs
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-05-28 11:08:46 +01:00
Ben Firshman
3a2c9c1016 Switch to Apache License 2.0
Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2014-05-28 10:59:21 +01:00
Aanand Prasad
cf3eed2cda Merge pull request #227 from orchardup/add-pythonunbuffered-to-django-docs
Add PYTHONUNBUFFERED=1 to Django tutorial
2014-05-22 17:12:23 +01:00
Ben Firshman
2ecd366905 Add PYTHONUNBUFFERED=1 to Django tutorial 2014-05-22 14:15:17 +01:00
Ben Firshman
d34dc45b78 Merge pull request #223 from orchardup/put-orchard-in-sidebar
Put Orchard in docs sidebar
2014-05-14 16:35:37 +01:00
Aanand Prasad
8394e84099 Compress the sidebar a bit 2014-05-14 16:32:39 +01:00
Ben Firshman
adda3a7f79 Put Orchard in docs sidebar 2014-05-14 13:05:40 +01:00
Aanand Prasad
52d0f4d9e7 Merge pull request #206 from orchardup/remove-intermediate-build-containers
Remove intermediate build containers
2014-05-12 11:59:01 +01:00
Ben Firshman
c1a38d787d Fix 0.4.1 release notes 2014-05-12 11:45:55 +01:00
Ben Firshman
7879dfd3fd Fix deploy docs script 2014-05-09 10:59:44 +01:00
Ben Firshman
cd1c8b2f09 Update docs to Docker 0.11.1 2014-05-09 10:53:12 +01:00
Ben Firshman
7a9228ad75 Remove intermediate build containers
Docker does this by default now.
2014-05-08 15:40:53 +01:00
Aanand Prasad
98ceb62202 Update Fig version on install page 2014-05-08 13:10:13 +01:00
Ben Firshman
b99bb64487 Add sanity check to OS X build script 2014-05-08 13:03:21 +01:00
Ben Firshman
580affa5f3 Add sanity check to linux build script 2014-05-08 13:02:50 +01:00
Ben Firshman
d600b3498b Remove entrypoint from dockerfile 2014-05-08 13:00:39 +01:00
Aanand Prasad
f0eaf84cb9 Merge pull request #217 from orchardup/0.4.1
Ship 0.4.1
2014-05-08 12:51:35 +01:00
Ben Firshman
257a171c0c Ship 0.4.1 2014-05-08 12:43:09 +01:00
Ben Firshman
3c5e818b49 Update install docs for Docker 0.11.0 2014-05-08 12:21:46 +01:00
Ben Firshman
c3c8395cef Merge pull request #215 from marksteve/docker-0.11
Docker 0.11
2014-05-08 10:10:19 +01:00
Mark Steve Samson
38c008e527 Fix index error when getting ghost state of containers 2014-05-08 12:39:15 +08:00
Aanand Prasad
a3d8f7d113 Remove hash from gif URL 2014-05-06 17:13:56 +01:00
Ben Firshman
65a642097c Merge pull request #211 from servebox/project-name
Add the ability to configure the project name using a command line option
2014-05-05 10:50:48 +01:00
Ben Firshman
dff9aa6f0c Add installation and entrypoint to dockerfile 2014-05-05 10:50:23 +01:00
Ben Firshman
ab145b5365 Fix pull requests failing on travis 2014-05-05 10:50:10 +01:00
Jef Mathiot
5878fe3834 Add the ability to configure the project name 2014-05-02 18:00:58 +02:00
Aanand Prasad
715e29d7ba Merge pull request #207 from orchardup/fig-run-correct-exit-code
Return correct exit code from fig run
2014-05-01 18:21:40 +01:00
Ben Firshman
983337401c Return correct exit code from fig run
Closes #197
2014-05-01 18:17:12 +01:00
Ben Firshman
8251dec587 Add docs, build and dist to clean script 2014-05-01 17:17:23 +01:00
Ben Firshman
52f994cf04 Remove /docs/.git-gh-pages from gitignore
It's inside /docs/_site
2014-05-01 17:17:06 +01:00
Ben Firshman
3d4b5cfbfe Update version of docker-osx 2014-05-01 15:34:21 +01:00
Aanand Prasad
33b057bfaf Update tag in docker-osx download URL 2014-05-01 10:51:42 +01:00
Aanand Prasad
629fe771df Update domain in docker-osx download URL 2014-04-30 16:37:20 +01:00
Aanand Prasad
5e6f175b5f Update download URLs on install page 2014-04-30 16:26:46 +01:00
38 changed files with 509 additions and 188 deletions

2
.gitignore vendored
View File

@@ -3,5 +3,5 @@
/build
/dist
/docs/_site
/docs/.git-gh-pages
/venv
fig.spec

View File

@@ -12,13 +12,13 @@ install:
- 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:
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && orchard hosts rm -f $TRAVIS_JOB_ID'
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && orchard hosts create $TRAVIS_JOB_ID || false'
- '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
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && script/travis-integration || false'
- 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then script/travis-integration; fi'
after_script:
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && orchard hosts rm -f $TRAVIS_JOB_ID || false'
- 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then orchard hosts rm -f $TRAVIS_JOB_ID; fi'
deploy:
provider: pypi
user: orchard

View File

@@ -1,6 +1,18 @@
Change log
==========
0.4.2 (2014-06-18)
------------------
- Fix various encoding errors when using `fig run`, `fig up` and `fig build`.
0.4.1 (2014-05-08)
------------------
- Add support for Docker 0.11.0. (Thanks @marksteve!)
- Make project name configurable. (Thanks @jefmathiot!)
- Return correct exit code from `fig run`.
0.4.0 (2014-04-29)
------------------

View File

@@ -1,5 +1,7 @@
# Contributing to Fig
## Development environment
If you're looking contribute to [Fig](http://orchardup.github.io/fig/)
but you're new to the project or maybe even to Python, here are the steps
that should get you started.
@@ -27,4 +29,47 @@ 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
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/)):
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(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
(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
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(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.
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

View File

@@ -5,6 +5,7 @@ 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 chown -R user /code/
USER user

215
LICENSE
View File

@@ -1,24 +1,191 @@
Copyright (c) 2013, Orchard Laboratories Ltd.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The names of its contributors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2014 Orchard Laboratories Ltd.
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.

View File

@@ -32,7 +32,7 @@ db:
Then type `fig up`, and Fig will start and run your entire app:
![example fig run](https://orchardup.com/static/images/fig-example-large.f96065fc9e22.gif)
![example fig run](https://orchardup.com/static/images/fig-example-large.gif)
There are commands to:

View File

@@ -44,10 +44,12 @@
</ul>
<ul class="nav">
<li><a href="https://github.com/orchardup/fig">Fig on GitHub</a></li>
<li><a href="https://twitter.com/orchardup">Follow us on Twitter</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>

View File

@@ -58,7 +58,7 @@ img {
.logo {
font-family: 'Lilita One', sans-serif;
font-size: 80px;
font-size: 64px;
margin: 20px 0 40px 0;
}
@@ -68,8 +68,8 @@ img {
}
.logo img {
width: 80px;
vertical-align: -17px;
width: 60px;
vertical-align: -8px;
}
.mobile-logo {
@@ -77,13 +77,18 @@ img {
}
.sidebar {
font-size: 16px;
font-size: 15px;
color: #777;
}
.sidebar a {
color: #a41211;
}
.sidebar p {
margin: 10px 0;
}
@media (max-width: 767px) {
.sidebar {
text-align: center;
@@ -101,7 +106,8 @@ img {
}
.logo {
margin-top: 40px;
margin-top: 30px;
margin-bottom: 30px;
}
.content h1 {
@@ -116,6 +122,7 @@ img {
width: 280px;
overflow-y: auto;
padding-left: 40px;
padding-right: 10px;
border-right: 1px solid #ccc;
}
@@ -126,12 +133,12 @@ img {
}
.nav {
margin: 20px 0;
margin: 15px 0;
}
.nav li a {
display: block;
padding: 8px 0;
padding: 5px 0;
line-height: 1.2;
text-decoration: none;
}

View File

@@ -11,6 +11,7 @@ Let's use Fig to set up and run a Django/PostgreSQL app. Before starting, you'll
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
ENV PYTHONUNBUFFERED 1
RUN apt-get update -qq && apt-get install -y python-psycopg2
RUN mkdir /code
WORKDIR /code
@@ -18,7 +19,7 @@ Let's set up the three files that'll get us started. First, our app is going to
RUN pip install -r requirements.txt
ADD . /code/
That'll install our application inside an image with Python installed alongside all of our Python dependencies. For more information on how to write Dockerfiles, see the [Dockerfile tutorial](https://www.docker.io/learn/dockerfile/) and the [Dockerfile reference](http://docs.docker.io/en/latest/reference/builder/).
That'll install our application inside an image with Python installed alongside all of our Python dependencies. 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/).
Second, we define our Python dependencies in a file called `requirements.txt`:

View File

@@ -30,7 +30,7 @@ db:
Then type `fig up`, and Fig will start and run your entire app:
![example fig run](https://orchardup.com/static/images/fig-example-large.f96065fc9e22.gif)
![example fig run](https://orchardup.com/static/images/fig-example-large.gif)
There are commands to:
@@ -39,8 +39,6 @@ There are commands to:
- 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.
Quick start
-----------
@@ -87,7 +85,7 @@ Next, we want to create a Docker image containing all of our app's dependencies.
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 [Dockerfile tutorial](https://www.docker.io/learn/dockerfile/) and the [Dockerfile reference](http://docs.docker.io/en/latest/reference/builder/).
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/).
We then define a set of services using `fig.yml`:

View File

@@ -6,9 +6,9 @@ title: Installing Fig
Installing Fig
==============
First, install Docker version 0.10.0. If you're on OS X, you can use [docker-osx](https://github.com/noplay/docker-osx):
First, install Docker version 1.0.0. If you're on OS X, you can use [docker-osx](https://github.com/noplay/docker-osx):
$ curl https://raw.github.com/noplay/docker-osx/0.10.0/docker-osx > /usr/local/bin/docker-osx
$ 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
@@ -16,12 +16,12 @@ Docker has guides for [Ubuntu](http://docs.docker.io/en/latest/installation/ubun
Next, install Fig. On OS X:
$ curl -L https://github.com/orchardup/fig/releases/download/0.3.2/darwin > /usr/local/bin/fig
$ curl -L https://github.com/orchardup/fig/releases/download/0.4.2/darwin > /usr/local/bin/fig
$ chmod +x /usr/local/bin/fig
On 64-bit Linux:
$ curl -L https://github.com/orchardup/fig/releases/download/0.3.2/linux > /usr/local/bin/fig
$ curl -L https://github.com/orchardup/fig/releases/download/0.4.2/linux > /usr/local/bin/fig
$ chmod +x /usr/local/bin/fig
Fig is also available as a Python package if you're on another platform (or if you prefer that sort of thing):

View File

@@ -18,7 +18,7 @@ Let's set up the three files that'll get us started. First, our app is going to
RUN bundle install
ADD . /myapp
That'll put our application code inside an image with Ruby, Bundler and all our dependencies. For more information on how to write Dockerfiles, see the [Dockerfile tutorial](https://www.docker.io/learn/dockerfile/) and the [Dockerfile reference](http://docs.docker.io/en/latest/reference/builder/).
That'll put our application code inside an image with Ruby, Bundler and all our dependencies. 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, we have a bootstrap `Gemfile` which just loads Rails. It'll be overwritten in a moment by `rails new`.

View File

@@ -17,7 +17,7 @@ FROM orchardup/php5
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 [Dockerfile tutorial](https://www.docker.io/learn/dockerfile/) and the [Dockerfile reference](http://docs.docker.io/en/latest/reference/builder/).
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:

View File

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

View File

@@ -24,6 +24,7 @@ class Command(DocoptCommand):
def __init__(self):
self.yaml_path = os.environ.get('FIG_FILE', None)
self.explicit_project_name = None
def dispatch(self, *args, **kwargs):
try:
@@ -44,6 +45,8 @@ class Command(DocoptCommand):
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
@@ -70,6 +73,8 @@ class Command(DocoptCommand):
@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'

View File

@@ -10,16 +10,17 @@ from .utils import split_buffer
class LogPrinter(object):
def __init__(self, containers, attach_params=None):
def __init__(self, containers, attach_params=None, output=sys.stdout):
self.containers = containers
self.attach_params = attach_params or {}
self.prefix_width = self._calculate_prefix_width(containers)
self.generators = self._make_log_generators()
self.output = output
def run(self):
mux = Multiplexer(self.generators)
for line in mux.loop():
sys.stdout.write(line.encode(sys.__stdout__.encoding or 'utf-8'))
self.output.write(line)
def _calculate_prefix_width(self, containers):
"""
@@ -45,12 +46,12 @@ class LogPrinter(object):
return generators
def _make_log_generator(self, container, color_fn):
prefix = color_fn(self._generate_prefix(container))
prefix = color_fn(self._generate_prefix(container)).encode('utf-8')
# Attach to container before log printer starts running
line_generator = split_buffer(self._attach(container), '\n')
for line in line_generator:
yield prefix + line.decode('utf-8')
yield prefix + line
exit_code = container.wait()
yield color_fn("%s exited with code %s\n" % (container.name, exit_code))

View File

@@ -71,9 +71,10 @@ class TopLevelCommand(Command):
fig -h|--help
Options:
--verbose Show more output
--version Print version and exit
-f, --file FILE Specify an alternate fig file (default: fig.yml)
--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
@@ -233,10 +234,11 @@ class TopLevelCommand(Command):
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']:
container.wait()
log.info("Removing %s..." % container.name)
self.client.remove_container(container.id)
sys.exit(exit_code)
def scale(self, options):
"""

View File

@@ -81,7 +81,7 @@ class SocketClient:
chunk = socket.recv(4096)
if chunk:
stream.write(chunk.encode(stream.encoding or 'utf-8'))
stream.write(chunk)
stream.flush()
else:
break

View File

@@ -78,7 +78,7 @@ class Container(object):
def human_readable_state(self):
self.inspect_if_not_inspected()
if self.dictionary['State']['Running']:
if self.dictionary['State']['Ghost']:
if self.dictionary['State'].get('Ghost'):
return 'Ghost'
else:
return 'Up'

83
fig/progress_stream.py Normal file
View File

@@ -0,0 +1,83 @@
import json
import os
import codecs
class StreamOutputError(Exception):
pass
def stream_output(output, stream):
is_terminal = hasattr(stream, 'fileno') and os.isatty(stream.fileno())
stream = codecs.getwriter('utf-8')(stream)
all_events = []
lines = {}
diff = 0
for chunk in output:
event = json.loads(chunk)
all_events.append(event)
if 'progress' in event or 'progressDetail' in event:
image_id = event['id']
if image_id in lines:
diff = len(lines) - lines[image_id]
else:
lines[image_id] = len(lines)
stream.write("\n")
diff = 0
if is_terminal:
# move cursor up `diff` rows
stream.write("%c[%dA" % (27, diff))
print_output_event(event, stream, is_terminal)
if 'id' in event and is_terminal:
# move cursor back down
stream.write("%c[%dB" % (27, diff))
stream.flush()
return all_events
def print_output_event(event, stream, is_terminal):
if 'errorDetail' in event:
raise StreamOutputError(event['errorDetail']['message'])
terminator = ''
if is_terminal and 'stream' not in event:
# erase current line
stream.write("%c[2K\r" % 27)
terminator = "\r"
pass
elif 'progressDetail' in event:
return
if 'time' in event:
stream.write("[%s] " % event['time'])
if 'id' in event:
stream.write("%s: " % event['id'])
if 'from' in event:
stream.write("(from %s) " % event['from'])
status = event.get('status', '')
if 'progress' in event:
stream.write("%s %s%s" % (status, event['progress'], terminator))
elif 'progressDetail' in event:
detail = event['progressDetail']
if 'current' in detail:
percentage = float(detail['current']) / float(detail['total']) * 100
stream.write('%s (%.1f%%)%s' % (status, percentage, terminator))
else:
stream.write('%s%s' % (status, terminator))
elif 'stream' in event:
stream.write("%s%s" % (event['stream'], terminator))
else:
stream.write("%s%s\n" % (status, terminator))

View File

@@ -5,8 +5,8 @@ import logging
import re
import os
import sys
import json
from .container import Container
from .progress_stream import stream_output, StreamOutputError
log = logging.getLogger(__name__)
@@ -305,7 +305,8 @@ class Service(object):
build_output = self.client.build(
self.options['build'],
tag=self._build_tag_name(),
stream=True
stream=True,
rm=True
)
try:
@@ -342,84 +343,6 @@ class Service(object):
return True
class StreamOutputError(Exception):
pass
def stream_output(output, stream):
is_terminal = hasattr(stream, 'fileno') and os.isatty(stream.fileno())
all_events = []
lines = {}
diff = 0
for chunk in output:
event = json.loads(chunk)
all_events.append(event)
if 'progress' in event or 'progressDetail' in event:
image_id = event['id']
if image_id in lines:
diff = len(lines) - lines[image_id]
else:
lines[image_id] = len(lines)
stream.write("\n")
diff = 0
if is_terminal:
# move cursor up `diff` rows
stream.write("%c[%dA" % (27, diff))
print_output_event(event, stream, is_terminal)
if 'id' in event and is_terminal:
# move cursor back down
stream.write("%c[%dB" % (27, diff))
stream.flush()
return all_events
def print_output_event(event, stream, is_terminal):
if 'errorDetail' in event:
raise StreamOutputError(event['errorDetail']['message'])
terminator = ''
if is_terminal and 'stream' not in event:
# erase current line
stream.write("%c[2K\r" % 27)
terminator = "\r"
pass
elif 'progressDetail' in event:
return
if 'time' in event:
stream.write("[%s] " % event['time'])
if 'id' in event:
stream.write("%s: " % event['id'])
if 'from' in event:
stream.write("(from %s) " % event['from'])
status = event.get('status', '')
if 'progress' in event:
stream.write("%s %s%s" % (status, event['progress'], terminator))
elif 'progressDetail' in event:
detail = event['progressDetail']
if 'current' in detail:
percentage = float(detail['current']) / float(detail['total']) * 100
stream.write('%s (%.1f%%)%s' % (status, percentage, terminator))
else:
stream.write('%s%s' % (status, terminator))
elif 'stream' in event:
stream.write("%s%s" % (event['stream'], terminator))
else:
stream.write("%s%s\n" % (status, terminator))
NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(run_)?(\d+)$')

View File

@@ -1,5 +1,7 @@
#!/bin/sh
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

View File

@@ -5,3 +5,4 @@ virtualenv venv
venv/bin/pip install pyinstaller==2.1
venv/bin/pip install .
venv/bin/pyinstaller -F bin/fig
dist/fig --version

View File

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

View File

@@ -21,8 +21,7 @@ git reset --soft origin/gh-pages
echo ".git-gh-pages" > .gitignore
git add -u
git add .
git add -A .
git commit -m "update" || echo "didn't commit"
git push origin master:gh-pages

View File

@@ -1,2 +1,2 @@
#!/bin/sh
nosetests
PYTHONIOENCODING=ascii nosetests $@

View File

@@ -35,7 +35,7 @@ setup(
url='http://orchardup.github.io/fig/',
author='Orchard Laboratories Ltd.',
author_email='hello@orchardup.com',
license='BSD',
license='Apache License 2.0',
packages=find_packages(),
include_package_data=True,
test_suite='nose.collector',

View File

@@ -1,3 +1,3 @@
definedinyamlnotyml:
image: ubuntu
image: busybox:latest
command: /bin/sleep 300

View File

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

View File

@@ -1,3 +1,3 @@
yetanother:
image: ubuntu
image: busybox:latest
command: /bin/sleep 300

View File

@@ -1,2 +1,2 @@
FROM ubuntu
FROM busybox:latest
CMD echo "success"

View File

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

View File

@@ -10,7 +10,7 @@ class DockerClientTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.client = Client(docker_url())
cls.client.pull('ubuntu', tag='latest')
cls.client.pull('busybox', tag='latest')
def setUp(self):
for c in self.client.containers(all=True):
@@ -28,7 +28,7 @@ class DockerClientTestCase(unittest.TestCase):
project='figtest',
name=name,
client=self.client,
image="ubuntu",
image="busybox:latest",
**kwargs
)

View File

@@ -6,7 +6,7 @@ class ContainerTest(unittest.TestCase):
def test_from_ps(self):
container = Container.from_ps(None, {
"Id":"abc",
"Image":"ubuntu:12.04",
"Image":"busybox:latest",
"Command":"sleep 300",
"Created":1387384730,
"Status":"Up 8 seconds",
@@ -17,7 +17,7 @@ class ContainerTest(unittest.TestCase):
}, has_been_inspected=True)
self.assertEqual(container.dictionary, {
"ID": "abc",
"Image":"ubuntu:12.04",
"Image":"busybox:latest",
"Name": "/figtest_db_1",
})
@@ -39,7 +39,7 @@ class ContainerTest(unittest.TestCase):
def test_number(self):
container = Container.from_ps(None, {
"Id":"abc",
"Image":"ubuntu:12.04",
"Image":"busybox:latest",
"Command":"sleep 300",
"Created":1387384730,
"Status":"Up 8 seconds",
@@ -53,7 +53,7 @@ class ContainerTest(unittest.TestCase):
def test_name(self):
container = Container.from_ps(None, {
"Id":"abc",
"Image":"ubuntu:12.04",
"Image":"busybox:latest",
"Command":"sleep 300",
"Names":["/figtest_db_1"]
}, has_been_inspected=True)
@@ -62,7 +62,7 @@ class ContainerTest(unittest.TestCase):
def test_name_without_project(self):
container = Container.from_ps(None, {
"Id":"abc",
"Image":"ubuntu:12.04",
"Image":"busybox:latest",
"Command":"sleep 300",
"Names":["/figtest_db_1"]
}, has_been_inspected=True)

View File

@@ -0,0 +1,57 @@
from __future__ import unicode_literals
from __future__ import absolute_import
import os
from fig.cli.log_printer import LogPrinter
from .. import unittest
class LogPrinterTest(unittest.TestCase):
def test_single_container(self):
def reader(*args, **kwargs):
yield "hello\nworld"
container = MockContainer(reader)
output = run_log_printer([container])
self.assertIn('hello', output)
self.assertIn('world', output)
def test_unicode(self):
glyph = u'\u2022'.encode('utf-8')
def reader(*args, **kwargs):
yield glyph + b'\n'
container = MockContainer(reader)
output = run_log_printer([container])
self.assertIn(glyph, output)
def run_log_printer(containers):
r, w = os.pipe()
reader, writer = os.fdopen(r, 'r'), os.fdopen(w, 'w')
printer = LogPrinter(containers, output=writer)
printer.run()
writer.close()
return reader.read()
class MockContainer(object):
def __init__(self, reader):
self._reader = reader
@property
def name(self):
return 'myapp_web_1'
@property
def name_without_project(self):
return 'web_1'
def attach(self, *args, **kwargs):
return self._reader()
def wait(self, *args, **kwargs):
return 0

View File

@@ -8,29 +8,29 @@ class ProjectTest(unittest.TestCase):
project = Project.from_dicts('figtest', [
{
'name': 'web',
'image': 'ubuntu'
'image': 'busybox:latest'
},
{
'name': 'db',
'image': 'ubuntu'
}
'image': 'busybox:latest'
},
], None)
self.assertEqual(len(project.services), 2)
self.assertEqual(project.get_service('web').name, 'web')
self.assertEqual(project.get_service('web').options['image'], 'ubuntu')
self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
self.assertEqual(project.get_service('db').name, 'db')
self.assertEqual(project.get_service('db').options['image'], 'ubuntu')
self.assertEqual(project.get_service('db').options['image'], 'busybox:latest')
def test_from_dict_sorts_in_dependency_order(self):
project = Project.from_dicts('figtest', [
{
'name': 'web',
'image': 'ubuntu',
'image': 'busybox:latest',
'links': ['db'],
},
{
'name': 'db',
'image': 'ubuntu'
'image': 'busybox:latest'
}
], None)
@@ -40,22 +40,22 @@ class ProjectTest(unittest.TestCase):
def test_from_config(self):
project = Project.from_config('figtest', {
'web': {
'image': 'ubuntu',
'image': 'busybox:latest',
},
'db': {
'image': 'ubuntu',
'image': 'busybox:latest',
},
}, None)
self.assertEqual(len(project.services), 2)
self.assertEqual(project.get_service('web').name, 'web')
self.assertEqual(project.get_service('web').options['image'], 'ubuntu')
self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
self.assertEqual(project.get_service('db').name, 'db')
self.assertEqual(project.get_service('db').options['image'], 'ubuntu')
self.assertEqual(project.get_service('db').options['image'], 'busybox:latest')
def test_from_config_throws_error_when_not_dict(self):
with self.assertRaises(ConfigurationError):
project = Project.from_config('figtest', {
'web': 'ubuntu',
'web': 'busybox:latest',
}, None)
def test_get_service(self):
@@ -63,7 +63,7 @@ class ProjectTest(unittest.TestCase):
project='figtest',
name='web',
client=None,
image="ubuntu",
image="busybox:latest",
)
project = Project('test', [web], None)
self.assertEqual(project.get_service('web'), web)

View File

@@ -6,32 +6,47 @@ from .. import unittest
class SplitBufferTest(unittest.TestCase):
def test_single_line_chunks(self):
def reader():
yield "abc\n"
yield "def\n"
yield "ghi\n"
yield b'abc\n'
yield b'def\n'
yield b'ghi\n'
self.assertEqual(list(split_buffer(reader(), '\n')), ["abc\n", "def\n", "ghi\n"])
self.assert_produces(reader, [b'abc\n', b'def\n', b'ghi\n'])
def test_no_end_separator(self):
def reader():
yield "abc\n"
yield "def\n"
yield "ghi"
yield b'abc\n'
yield b'def\n'
yield b'ghi'
self.assertEqual(list(split_buffer(reader(), '\n')), ["abc\n", "def\n", "ghi"])
self.assert_produces(reader, [b'abc\n', b'def\n', b'ghi'])
def test_multiple_line_chunk(self):
def reader():
yield "abc\ndef\nghi"
yield b'abc\ndef\nghi'
self.assertEqual(list(split_buffer(reader(), '\n')), ["abc\n", "def\n", "ghi"])
self.assert_produces(reader, [b'abc\n', b'def\n', b'ghi'])
def test_chunked_line(self):
def reader():
yield "a"
yield "b"
yield "c"
yield "\n"
yield "d"
yield b'a'
yield b'b'
yield b'c'
yield b'\n'
yield b'd'
self.assertEqual(list(split_buffer(reader(), '\n')), ["abc\n", "d"])
self.assert_produces(reader, [b'abc\n', b'd'])
def test_preserves_unicode_sequences_within_lines(self):
string = u"a\u2022c\n".encode('utf-8')
def reader():
yield string
self.assert_produces(reader, [string])
def assert_produces(self, reader, expectations):
split = split_buffer(reader(), b'\n')
for (actual, expected) in zip(split, expectations):
self.assertEqual(type(actual), type(expected))
self.assertEqual(actual, expected)