Compare commits

..

12 Commits
0.2.0 ... 0.2.1

Author SHA1 Message Date
Ben Firshman
a8bc51b229 Merge pull request #80 from orchardup/ship-0.2.1
Ship 0.2.1
2014-02-04 18:17:26 -08:00
Aanand Prasad
6bad9484be Ship 0.2.1 2014-02-04 18:14:19 -08:00
Ben Firshman
d52f73b29a Merge pull request #79 from orchardup/strict-config
Throw an error if you specify an unrecognised option in `fig.yml`
2014-02-04 18:10:42 -08:00
Aanand Prasad
edf8f14ac0 Throw an error if you specify an unrecognised option in fig.yml
Closes #27.
2014-02-04 17:46:04 -08:00
Ben Firshman
9faa7e47ed Lock version of docker-osx on home page 2014-02-04 17:15:14 -08:00
Ben Firshman
f92317c5ee Merge pull request #77 from orchardup/friendlier-connection-error
Friendlier connection error for docker-osx users
2014-02-04 17:04:21 -08:00
Ben Firshman
fea3bc2eb1 Install docker-osx from tag 2014-02-04 15:47:12 -08:00
Aanand Prasad
2b89494405 Fix Ubuntu check - forgot to actually inspect the distro 2014-02-04 15:31:05 -08:00
Aanand Prasad
2bac1c10b0 Show installation instructions if it looks like Docker isn't installed 2014-02-04 15:19:50 -08:00
Aanand Prasad
5126649de4 Friendlier connection error for docker-osx users 2014-02-04 14:42:55 -08:00
Ben Firshman
690a7d6dd7 Add /build to gitignore 2014-02-03 16:01:30 -08:00
Ben Firshman
19c0c9bf06 Specify working docker-osx version in docs 2014-02-03 16:00:01 -08:00
9 changed files with 97 additions and 22 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,6 +1,11 @@
Change log
==========
0.2.1 (2014-02-04)
------------------
- General improvements to error reporting (#77, #79)
0.2.0 (2014-01-31)
------------------

View File

@@ -49,7 +49,7 @@ Let's get a basic Python web app running on Fig. It assumes a little knowledge o
First, install Docker. 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/master/docker-osx > /usr/local/bin/docker-osx
$ curl https://raw.github.com/noplay/docker-osx/0.7.6/docker-osx > /usr/local/bin/docker-osx
$ chmod +x /usr/local/bin/docker-osx
$ docker-osx shell

View File

@@ -8,7 +8,7 @@ Installing Fig
First, install Docker. 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/master/docker-osx > /usr/local/bin/docker-osx
$ curl https://raw.github.com/noplay/docker-osx/0.7.6/docker-osx > /usr/local/bin/docker-osx
$ chmod +x /usr/local/bin/docker-osx
$ docker-osx shell

View File

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

View File

@@ -7,11 +7,13 @@ import logging
import os
import re
import yaml
import six
from ..project import Project
from ..service import ConfigError
from .docopt_command import DocoptCommand
from .formatter import Formatter
from .utils import cached_property, docker_url
from .utils import cached_property, docker_url, call_silently, is_mac, is_ubuntu
from .errors import UserError
log = logging.getLogger(__name__)
@@ -23,7 +25,29 @@ class Command(DocoptCommand):
try:
super(Command, self).dispatch(*args, **kwargs)
except ConnectionError:
raise UserError("""
if call_silently(['which', 'docker']) != 0:
if is_mac():
raise UserError("""
Couldn't connect to Docker daemon. You might need to install docker-osx:
https://github.com/noplay/docker-osx
""")
elif is_ubuntu():
raise UserError("""
Couldn't connect to Docker daemon. You might need to install Docker:
http://docs.docker.io/en/latest/installation/ubuntulinux/
""")
else:
raise UserError("""
Couldn't connect to Docker daemon. You might need to install Docker:
http://docs.docker.io/en/latest/installation/
""")
elif call_silently(['which', 'docker-osx']) == 0:
raise UserError("Couldn't connect to Docker daemon - you might need to run `docker-osx shell`.")
else:
raise UserError("""
Couldn't connect to Docker daemon at %s - is it running?
If it's at a non-standard location, specify the URL with the DOCKER_HOST environment variable.
@@ -47,7 +71,10 @@ If it's at a non-standard location, specify the URL with the DOCKER_HOST environ
exit(1)
return Project.from_config(self.project_name, config, self.client)
try:
return Project.from_config(self.project_name, config, self.client)
except ConfigError as e:
raise UserError(six.text_type(e))
@cached_property
def project_name(self):

View File

@@ -4,6 +4,8 @@ from __future__ import division
import datetime
import os
import socket
import subprocess
import platform
from .errors import UserError
@@ -108,3 +110,19 @@ def split_buffer(reader, separator):
if len(buffered) > 0:
yield buffered
def call_silently(*args, **kwargs):
"""
Like subprocess.call(), but redirects stdout and stderr to /dev/null.
"""
with open(os.devnull, 'w') as shutup:
return subprocess.call(*args, stdout=shutup, stderr=shutup, **kwargs)
def is_mac():
return platform.system() == 'Darwin'
def is_ubuntu():
return platform.system() == 'Linux' and platform.linux_distribution()[0] == 'Ubuntu'

View File

@@ -10,6 +10,14 @@ from .container import Container
log = logging.getLogger(__name__)
DOCKER_CONFIG_KEYS = ['image', 'command', 'hostname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'volumes_from', 'entrypoint']
DOCKER_CONFIG_HINTS = {
'link': 'links',
'port': 'ports',
'volume': 'volumes',
}
class BuildError(Exception):
pass
@@ -18,14 +26,27 @@ 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 ValueError('Invalid name: %s' % name)
raise ConfigError('Invalid name: %s' % name)
if not re.match('^[a-zA-Z0-9]+$', project):
raise ValueError('Invalid project: %s' % project)
raise ConfigError('Invalid project: %s' % project)
if 'image' in options and 'build' in options:
raise ValueError('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)
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']
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
@@ -218,8 +239,7 @@ class Service(object):
return links
def _get_container_options(self, override_options, one_off=False):
keys = ['image', 'command', 'hostname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'volumes_from', 'entrypoint']
container_options = dict((k, self.options[k]) for k in keys if k in self.options)
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)

View File

@@ -1,30 +1,34 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from fig import Service
from fig.service import CannotBeScaledError
from fig.service import CannotBeScaledError, ConfigError
from .testcases import DockerClientTestCase
class ServiceTest(DockerClientTestCase):
def test_name_validations(self):
self.assertRaises(ValueError, lambda: Service(name=''))
self.assertRaises(ConfigError, lambda: Service(name=''))
self.assertRaises(ValueError, lambda: Service(name=' '))
self.assertRaises(ValueError, lambda: Service(name='/'))
self.assertRaises(ValueError, lambda: Service(name='!'))
self.assertRaises(ValueError, lambda: Service(name='\xe2'))
self.assertRaises(ValueError, lambda: Service(name='_'))
self.assertRaises(ValueError, lambda: Service(name='____'))
self.assertRaises(ValueError, lambda: Service(name='foo_bar'))
self.assertRaises(ValueError, lambda: Service(name='__foo_bar__'))
self.assertRaises(ConfigError, lambda: Service(name=' '))
self.assertRaises(ConfigError, lambda: Service(name='/'))
self.assertRaises(ConfigError, lambda: Service(name='!'))
self.assertRaises(ConfigError, lambda: Service(name='\xe2'))
self.assertRaises(ConfigError, lambda: Service(name='_'))
self.assertRaises(ConfigError, lambda: Service(name='____'))
self.assertRaises(ConfigError, lambda: Service(name='foo_bar'))
self.assertRaises(ConfigError, lambda: Service(name='__foo_bar__'))
Service('a')
Service('foo')
def test_project_validation(self):
self.assertRaises(ValueError, lambda: Service(name='foo', project='_'))
self.assertRaises(ConfigError, lambda: Service(name='foo', project='_'))
Service(name='foo', project='bar')
def test_config_validation(self):
self.assertRaises(ConfigError, lambda: Service(name='foo', port=['8000']))
Service(name='foo', ports=['8000'])
def test_containers(self):
foo = self.create_service('foo')
bar = self.create_service('bar')