Compare commits

...

66 Commits
0.5.0 ... 0.5.2

Author SHA1 Message Date
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
32 changed files with 426 additions and 115 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.egg-info
*.pyc
.tox
/build
/dist
/docs/_site

View File

@@ -16,6 +16,7 @@ before_script:
- 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then orchard hosts create $TRAVIS_JOB_ID; fi'
script:
- nosetests tests/unit
- flake8 fig
- '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'

View File

@@ -1,6 +1,28 @@
Change log
==========
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)
------------------
@@ -23,12 +45,20 @@ Change log
- 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 and @mozz100 for their help with this release!
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

@@ -2,7 +2,7 @@
## Development environment
If you're looking contribute to [Fig](http://orchardup.github.io/fig/)
If you're looking contribute to [Fig](http://www.fig.sh/)
but you're new to the project or maybe even to Python, here are the steps
that should get you started.
@@ -73,3 +73,21 @@ The easiest way to do this is to use the `--signoff` flag when committing. E.g.:
$ git commit --signoff
## Release process
1. Open pull request that:
- Updates version in `fig/__init__.py`
- Updates version in `docs/install.md`
- Adds release notes to `CHANGES.md`
2. Create unpublished GitHub release with release notes
3. Build Linux version on any Docker host with `script/build-linux` and attach to release
4. Build OS X version on Mountain Lion with `script/build-osx` and attach to release
5. Publish GitHub release, creating tag
6. Update website with `script/deploy-docs`

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)
Nathan LeClaire <nathan.leclaire@gmail.com> (@nathanleclaire)

View File

@@ -46,4 +46,4 @@ Fig is a project from [Orchard](https://orchardup.com), a Docker hosting 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/).

1
docs/CNAME Normal file
View File

@@ -0,0 +1 @@
www.fig.sh

View File

@@ -7,6 +7,7 @@
<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' }}">
<link rel="canonical" href="http://www.fig.sh{% if page.url =="/index.html" %}/{% else %}{{ page.url }}{% endif %}">
</head>
<body>
<div class="container">

View File

@@ -39,7 +39,7 @@ 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 [`fig.yml` reference](yml.html) for more information on how it works.
We can now start a Django project using `fig run`:

View File

@@ -6,9 +6,9 @@ title: Installing Fig
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):
First, install Docker version 1.0 or greater. If you're on OS X, you can use [docker-osx](https://github.com/noplay/docker-osx):
$ curl https://raw.githubusercontent.com/noplay/docker-osx/1.0.0/docker-osx > /usr/local/bin/docker-osx
$ curl https://raw.githubusercontent.com/noplay/docker-osx/1.1.1/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.4.2/darwin > /usr/local/bin/fig
$ curl -L https://github.com/orchardup/fig/releases/download/0.5.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.4.2/linux > /usr/local/bin/fig
$ curl -L https://github.com/orchardup/fig/releases/download/0.5.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

@@ -10,62 +10,140 @@ Each service defined in `fig.yml` must specify exactly one of `image` or `build`
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`.
```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 - Fig 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. Fig 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
### links
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`
```
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.
### 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
Mount paths as volumes, optionally specifying a path on the host machine (`HOST:CONTAINER`).
Note: Mapping local volumes is currently unsupported on boot2docker. We recommend you use [docker-osx](https://github.com/noplay/docker-osx) if want to map local volumes.
```
volumes:
- /var/lib/mysql
- cache/:/tmp/cache
```
-- Mount all of the volumes from another service or container
### volumes_from
Mount all of the volumes from another service or container.
```
volumes_from:
- service_name
- container_name
```
-- Add environment variables.
-- Environment variables with only a key are resolved to values on the host
-- machine, which can be helpful for secret or host-specific values.
### 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 Fig 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
```
-- Networking mode. Use the same values as the docker client --net parameter
### 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
```
### working\_dir, entrypoint, user, hostname, domainname, mem\_limit, privileged
Each of these is a single value, analogous to its [docker run](https://docs.docker.com/reference/run/) counterpart.
```
working_dir: /code
entrypoint: /code/entrypoint.sh
user: postgresql
hostname: foo
domainname: foo.com
mem_limit: 1000000000
privileged: true
```

View File

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

View File

@@ -8,7 +8,6 @@ import os
import re
import yaml
from ..packages import six
import sys
from ..project import Project
from ..service import ConfigError
@@ -19,11 +18,12 @@ 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._yaml_path = os.environ.get('FIG_FILE', None)
self.explicit_project_name = None
def dispatch(self, *args, **kwargs):
@@ -56,10 +56,7 @@ class Command(DocoptCommand):
@cached_property
def project(self):
try:
yaml_path = self.yaml_path
if yaml_path is None:
yaml_path = self.check_yaml_filename()
config = yaml.safe_load(open(yaml_path))
config = yaml.safe_load(open(self.yaml_path))
except IOError as e:
if e.errno == errno.ENOENT:
raise errors.FigFileNotFound(os.path.basename(e.filename))
@@ -72,7 +69,7 @@ class Command(DocoptCommand):
@cached_property
def project_name(self):
project = os.path.basename(os.getcwd())
project = os.path.basename(os.path.dirname(os.path.abspath(self.yaml_path)))
if self.explicit_project_name is not None:
project = self.explicit_project_name
project = re.sub(r'[^a-zA-Z0-9]', '', project)
@@ -84,8 +81,11 @@ class Command(DocoptCommand):
def formatter(self):
return Formatter()
def check_yaml_filename(self):
if os.path.exists(os.path.join(self.base_dir, 'fig.yaml')):
@cached_property
def yaml_path(self):
if self._yaml_path is not None:
return self._yaml_path
elif 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")
@@ -93,3 +93,7 @@ class Command(DocoptCommand):
return os.path.join(self.base_dir, 'fig.yaml')
else:
return os.path.join(self.base_dir, 'fig.yml')
@yaml_path.setter
def yaml_path(self, value):
self._yaml_path = value

View File

@@ -24,16 +24,7 @@ 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
setup_logging()
try:
command = TopLevelCommand()
command.sys_dispatch()
@@ -56,6 +47,18 @@ def main():
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|$))*)',
@@ -103,9 +106,13 @@ class TopLevelCommand(Command):
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...]
Usage: build [options] [SERVICE...]
Options:
--no-cache Do not use cache when building the image.
"""
self.project.build(service_names=options['SERVICE'])
no_cache = bool(options.get('--no-cache', False))
self.project.build(service_names=options['SERVICE'], no_cache=no_cache)
def help(self, options):
"""
@@ -206,7 +213,7 @@ class TopLevelCommand(Command):
running. If you do not want to start linked services, use
`fig run --no-deps SERVICE COMMAND [ARGS...]`.
Usage: run [options] SERVICE COMMAND [ARGS...]
Usage: run [options] SERVICE [COMMAND] [ARGS...]
Options:
-d Detached mode: Run container in the background, print
@@ -233,8 +240,13 @@ class TopLevelCommand(Command):
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': [options['COMMAND']] + options['ARGS'],
'command': command,
'tty': tty,
'stdin_open': not options['-d'],
}
@@ -269,13 +281,13 @@ class TopLevelCommand(Command):
try:
num = int(num)
except ValueError:
raise UserError('Number of containers for service "%s" is not a number' % service)
raise UserError('Number of containers for service "%s" is not a '
'number' % service_name)
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.
@@ -345,5 +357,6 @@ class TopLevelCommand(Command):
print("Gracefully stopping... (press Ctrl+C again to force)")
self.project.stop(service_names=service_names)
def list_containers(containers):
return ", ".join(c.name for c in containers)

View File

@@ -65,11 +65,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):
@@ -103,8 +103,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

@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from __future__ import absolute_import
class Container(object):
"""
Represents a Docker container, constructed from the output of

View File

@@ -38,6 +38,7 @@ def sort_service_dicts(services):
return sorted_services
class Project(object):
"""
A collection of services.
@@ -135,8 +136,8 @@ class Project(object):
volumes_from.append(service)
except NoSuchService:
try:
container = Container.from_id(client, volume_name)
volumes_from.append(Container.from_id(client, volume_name))
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']
@@ -154,10 +155,10 @@ class Project(object):
for service in reversed(self.get_services(service_names)):
service.kill(**options)
def build(self, service_names=None, **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(**options)
service.build(no_cache)
else:
log.info('%s uses an image, skipping' % service.name)
@@ -216,6 +217,6 @@ class ConfigurationError(Exception):
def __str__(self):
return self.msg
class DependencyError(ConfigurationError):
pass

View File

@@ -40,7 +40,7 @@ class ConfigError(ValueError):
class Service(object):
def __init__(self, name, client=None, project='default', links=[], volumes_from=[], **options):
def __init__(self, name, client=None, project='default', 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):
@@ -132,7 +132,6 @@ class Service(object):
self.remove_stopped()
def remove_stopped(self, **options):
for c in self.containers(stopped=True):
if not c.is_running:
@@ -177,8 +176,15 @@ class Service(object):
return tuples
def recreate_container(self, container, **override_options):
if container.is_running:
container.stop(timeout=1)
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,
@@ -205,7 +211,7 @@ class Service(object):
log.info("Starting %s..." % container.name)
return self.start_container(container, **options)
def start_container(self, container=None, intermediate_container=None,**override_options):
def start_container(self, container=None, intermediate_container=None, **override_options):
if container is None:
container = self.create_container(**override_options)
@@ -232,6 +238,7 @@ class Service(object):
privileged = options.get('privileged', False)
net = options.get('net', 'bridge')
dns = options.get('dns', None)
container.start(
links=self._get_links(link_to_self=override_options.get('one_off', False)),
@@ -240,6 +247,7 @@ class Service(object):
volumes_from=self._get_volumes_from(intermediate_container),
privileged=privileged,
network_mode=net,
dns=dns,
)
return container
@@ -333,31 +341,29 @@ class Service(object):
if 'environment' in container_options:
if isinstance(container_options['environment'], list):
container_options['environment'] = dict(split_env(e) for e in container_options['environment'])
container_options['environment'] = dict(resolve_env(k,v) for k,v in container_options['environment'].iteritems())
container_options['environment'] = dict(resolve_env(k, v) for k, v in container_options['environment'].iteritems())
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']
# net is only required for starting containers, not for creating them
if 'net' in container_options:
del container_options['net']
# Delete options which are only used when starting
for key in ['privileged', 'net', 'dns']:
if key in container_options:
del container_options[key]
return container_options
def build(self):
def build(self, no_cache=False):
log.info('Building %s...' % self.name)
build_output = self.client.build(
self.options['build'],
tag=self._build_tag_name(),
stream=True,
rm=True
rm=True,
nocache=no_cache,
)
try:
@@ -452,13 +458,15 @@ def split_port(port):
external_port = (external_ip,)
return internal_port, external_port
def split_env(env):
if '=' in env:
return env.split('=', 1)
else:
return env, None
def resolve_env(key,val):
def resolve_env(key, val):
if val is not None:
return key, val
elif key in os.environ:

View File

@@ -2,3 +2,4 @@ mock==1.0.1
nose==1.3.0
pyinstaller==2.1
unittest2
flake8

View File

@@ -3,4 +3,4 @@ PyYAML==3.10
requests==2.2.1
texttable==0.8.1
websocket-client==0.11.0
dockerpty==0.2.1
dockerpty==0.2.3

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 fig.egg-info

View File

@@ -1,2 +1,4 @@
#!/bin/sh
set -e
flake8 fig
PYTHONIOENCODING=ascii nosetests $@

View File

@@ -32,9 +32,8 @@ 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',
url='http://www.fig.sh/',
author='Docker, Inc.',
license='Apache License 2.0',
packages=find_packages(),
include_package_data=True,

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ class CLITestCase(DockerClientTestCase):
def test_ps(self, mock_stdout):
self.command.project.get_service('simple').create_container()
self.command.dispatch(['ps'], None)
self.assertIn('fig_simple_1', mock_stdout.getvalue())
self.assertIn('simplefigfile_simple_1', mock_stdout.getvalue())
@patch('sys.stdout', new_callable=StringIO)
def test_ps_default_figfile(self, mock_stdout):
@@ -31,9 +31,9 @@ class CLITestCase(DockerClientTestCase):
self.command.dispatch(['ps'], None)
output = mock_stdout.getvalue()
self.assertIn('fig_simple_1', output)
self.assertIn('fig_another_1', output)
self.assertNotIn('fig_yetanother_1', output)
self.assertIn('multiplefigfiles_simple_1', output)
self.assertIn('multiplefigfiles_another_1', output)
self.assertNotIn('multiplefigfiles_yetanother_1', output)
@patch('sys.stdout', new_callable=StringIO)
def test_ps_alternate_figfile(self, mock_stdout):
@@ -42,9 +42,25 @@ class CLITestCase(DockerClientTestCase):
self.command.dispatch(['-f', 'fig2.yml', 'ps'], None)
output = mock_stdout.getvalue()
self.assertNotIn('fig_simple_1', output)
self.assertNotIn('fig_another_1', output)
self.assertIn('fig_yetanother_1', output)
self.assertNotIn('multiplefigfiles_simple_1', output)
self.assertNotIn('multiplefigfiles_another_1', output)
self.assertIn('multiplefigfiles_yetanother_1', output)
@patch('sys.stdout', new_callable=StringIO)
def test_build_no_cache(self, mock_stdout):
self.command.base_dir = 'tests/fixtures/simple-dockerfile'
self.command.dispatch(['build', 'simple'], None)
mock_stdout.truncate(0)
cache_indicator = 'Using cache'
self.command.dispatch(['build', 'simple'], None)
output = mock_stdout.getvalue()
self.assertIn(cache_indicator, output)
mock_stdout.truncate(0)
self.command.dispatch(['build', '--no-cache', 'simple'], None)
output = mock_stdout.getvalue()
self.assertNotIn(cache_indicator, output)
def test_up(self):
self.command.dispatch(['up', '-d'], None)
@@ -109,7 +125,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(self.command.project.containers()), 0)
@patch('dockerpty.start')
def test_run_service_with_links(self, mock_stdout):
def test_run_service_with_links(self, __):
self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['run', 'web', '/bin/true'], None)
db = self.command.project.get_service('db')
@@ -118,18 +134,14 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(console.containers()), 0)
@patch('dockerpty.start')
def test_run_with_no_deps(self, mock_stdout):
mock_stdout.fileno = lambda: 1
def test_run_with_no_deps(self, __):
self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['run', '--no-deps', 'web', '/bin/true'], None)
db = self.command.project.get_service('db')
self.assertEqual(len(db.containers()), 0)
@patch('dockerpty.start')
def test_run_does_not_recreate_linked_containers(self, mock_stdout):
mock_stdout.fileno = lambda: 1
def test_run_does_not_recreate_linked_containers(self, __):
self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['up', '-d', 'db'], None)
db = self.command.project.get_service('db')
@@ -144,6 +156,30 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(old_ids, new_ids)
@patch('dockerpty.start')
def test_run_without_command(self, __):
self.command.base_dir = 'tests/fixtures/commands-figfile'
self.client.build('tests/fixtures/simple-dockerfile', tag='figtest_test')
for c in self.command.project.containers(stopped=True, one_off=True):
c.remove()
self.command.dispatch(['run', 'implicit'], None)
service = self.command.project.get_service('implicit')
containers = service.containers(stopped=True, one_off=True)
self.assertEqual(
[c.human_readable_command for c in containers],
[u'/bin/sh -c echo "success"'],
)
self.command.dispatch(['run', 'explicit'], None)
service = self.command.project.get_service('explicit')
containers = service.containers(stopped=True, one_off=True)
self.assertEqual(
[c.human_readable_command for c in containers],
[u'/bin/true'],
)
def test_rm(self):
service = self.command.project.get_service('simple')
service.create_container()
@@ -173,3 +209,4 @@ class CLITestCase(DockerClientTestCase):
self.command.scale({'SERVICE=NUM': ['simple=0', 'another=0']})
self.assertEqual(len(project.get_service('simple').containers()), 0)
self.assertEqual(len(project.get_service('another').containers()), 0)

View File

@@ -1,9 +1,49 @@
from __future__ import unicode_literals
from fig.project import Project, ConfigurationError
from fig.container import Container
from .testcases import DockerClientTestCase
class ProjectTest(DockerClientTestCase):
def test_volumes_from_service(self):
project = Project.from_config(
name='figtest',
config={
'data': {
'image': 'busybox:latest',
'volumes': ['/var/data'],
},
'db': {
'image': 'busybox:latest',
'volumes_from': ['data'],
},
},
client=self.client,
)
db = project.get_service('db')
data = project.get_service('data')
self.assertEqual(db.volumes_from, [data])
def test_volumes_from_container(self):
data_container = Container.create(
self.client,
image='busybox:latest',
volumes=['/var/data'],
name='figtest_data_container',
)
project = Project.from_config(
name='figtest',
config={
'db': {
'image': 'busybox:latest',
'volumes_from': ['figtest_data_container'],
},
},
client=self.client,
)
db = project.get_service('db')
self.assertEqual(db.volumes_from, [data_container])
def test_start_stop_kill_remove(self):
web = self.create_service('web')
db = self.create_service('db')

View File

@@ -113,12 +113,12 @@ class ServiceTest(DockerClientTestCase):
'db',
environment={'FOO': '1'},
volumes=['/var/db'],
entrypoint=['ps'],
command=['ax']
entrypoint=['sleep'],
command=['300']
)
old_container = service.create_container()
self.assertEqual(old_container.dictionary['Config']['Entrypoint'], ['ps'])
self.assertEqual(old_container.dictionary['Config']['Cmd'], ['ax'])
self.assertEqual(old_container.dictionary['Config']['Entrypoint'], ['sleep'])
self.assertEqual(old_container.dictionary['Config']['Cmd'], ['300'])
self.assertIn('FOO=1', old_container.dictionary['Config']['Env'])
self.assertEqual(old_container.name, 'figtest_db_1')
service.start_container(old_container)
@@ -134,8 +134,8 @@ class ServiceTest(DockerClientTestCase):
new_container = tuples[0][1]
self.assertEqual(intermediate_container.dictionary['Config']['Entrypoint'], ['echo'])
self.assertEqual(new_container.dictionary['Config']['Entrypoint'], ['ps'])
self.assertEqual(new_container.dictionary['Config']['Cmd'], ['ax'])
self.assertEqual(new_container.dictionary['Config']['Entrypoint'], ['sleep'])
self.assertEqual(new_container.dictionary['Config']['Cmd'], ['300'])
self.assertIn('FOO=2', new_container.dictionary['Config']['Env'])
self.assertEqual(new_container.name, 'figtest_db_1')
self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path)
@@ -145,6 +145,19 @@ class ServiceTest(DockerClientTestCase):
self.assertNotEqual(old_container.id, new_container.id)
self.assertRaises(APIError, lambda: self.client.inspect_container(intermediate_container.id))
def test_recreate_containers_when_containers_are_stopped(self):
service = self.create_service(
'db',
environment={'FOO': '1'},
volumes=['/var/db'],
entrypoint=['sleep'],
command=['300']
)
old_container = service.create_container()
self.assertEqual(len(service.containers(stopped=True)), 1)
service.recreate_containers()
self.assertEqual(len(service.containers(stopped=True)), 1)
def test_start_container_passes_through_options(self):
db = self.create_service('db')
db.start_container(environment={'FOO': 'BAR'})
@@ -247,7 +260,7 @@ class ServiceTest(DockerClientTestCase):
def test_port_with_explicit_interface(self):
service = self.create_service('web', ports=[
'127.0.0.1:8001:8000',
'0.0.0.0:9001:9000',
'0.0.0.0:9001:9000/udp',
])
container = service.start_container().inspect()
self.assertEqual(container['NetworkSettings']['Ports'], {
@@ -257,7 +270,7 @@ class ServiceTest(DockerClientTestCase):
'HostPort': '8001',
},
],
'9000/tcp': [
'9000/udp': [
{
'HostIp': '0.0.0.0',
'HostPort': '9001',
@@ -303,6 +316,16 @@ class ServiceTest(DockerClientTestCase):
container = service.start_container().inspect()
self.assertEqual(container['HostConfig']['NetworkMode'], 'host')
def test_dns_single_value(self):
service = self.create_service('web', dns='8.8.8.8')
container = service.start_container().inspect()
self.assertEqual(container['HostConfig']['Dns'], ['8.8.8.8'])
def test_dns_list(self):
service = self.create_service('web', dns=['8.8.8.8', '9.9.9.9'])
container = service.start_container().inspect()
self.assertEqual(container['HostConfig']['Dns'], ['8.8.8.8', '9.9.9.9'])
def test_working_dir_param(self):
service = self.create_service('container', working_dir='/working/dir/sample')
container = service.create_container().inspect()

View File

@@ -1,10 +1,35 @@
from __future__ import unicode_literals
from __future__ import absolute_import
import logging
import os
from .. import unittest
from fig.cli import main
from fig.cli.main import TopLevelCommand
from fig.packages.six import StringIO
class CLITestCase(unittest.TestCase):
def test_default_project_name(self):
cwd = os.getcwd()
try:
os.chdir('tests/fixtures/simple-figfile')
command = TopLevelCommand()
self.assertEquals('simplefigfile', command.project_name)
finally:
os.chdir(cwd)
def test_project_name_with_explicit_base_dir(self):
command = TopLevelCommand()
command.base_dir = 'tests/fixtures/simple-figfile'
self.assertEquals('simplefigfile', command.project_name)
def test_project_name_with_explicit_project_name(self):
command = TopLevelCommand()
command.explicit_project_name = 'explicit-project-name'
self.assertEquals('explicitprojectname', command.project_name)
def test_yaml_filename_check(self):
command = TopLevelCommand()
command.base_dir = 'tests/fixtures/longer-filename-figfile'
@@ -14,3 +39,8 @@ class CLITestCase(unittest.TestCase):
command = TopLevelCommand()
with self.assertRaises(SystemExit):
command.dispatch(['-h'], None)
def test_setup_logging(self):
main.setup_logging()
self.assertEqual(logging.getLogger().level, logging.DEBUG)
self.assertEqual(logging.getLogger('requests').propagate, False)

View File

@@ -33,6 +33,10 @@ class ServiceTest(unittest.TestCase):
self.assertEqual(internal_port, "2000")
self.assertEqual(external_port, ("127.0.0.1", "1000"))
internal_port, external_port = split_port("127.0.0.1:1000:2000/udp")
self.assertEqual(internal_port, "2000/udp")
self.assertEqual(external_port, ("127.0.0.1", "1000"))
internal_port, external_port = split_port("127.0.0.1::2000")
self.assertEqual(internal_port, "2000")
self.assertEqual(external_port, ("127.0.0.1",))

13
tox.ini
View File

@@ -2,7 +2,14 @@
envlist = py26,py27,py32,py33,pypy
[testenv]
deps =
-rrequirements.txt
-rrequirements-dev.txt
commands =
pip install -e {toxinidir}
pip install -e {toxinidir}[test]
python setup.py test
nosetests {posargs}
flake8 fig
[flake8]
# ignore line-length for now
ignore = E501,E203
exclude = fig/packages/