Resolves #369, add verbose output on --verbose flag

Signed-off-by: Daniel Nephin <dnephin@gmail.com>
This commit is contained in:
Daniel Nephin 2014-07-30 01:42:58 -04:00
parent f2bf7f9e0d
commit df7c2cc43f
9 changed files with 228 additions and 131 deletions

View File

@ -12,9 +12,10 @@ from ..packages import six
from ..project import Project from ..project import Project
from ..service import ConfigError from ..service import ConfigError
from .docopt_command import DocoptCommand from .docopt_command import DocoptCommand
from .formatter import Formatter from .utils import docker_url, call_silently, is_mac, is_ubuntu
from .utils import cached_property, docker_url, call_silently, is_mac, is_ubuntu from . import verbose_proxy
from . import errors from . import errors
from .. import __version__
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -22,10 +23,6 @@ log = logging.getLogger(__name__)
class Command(DocoptCommand): class Command(DocoptCommand):
base_dir = '.' base_dir = '.'
def __init__(self):
self._yaml_path = os.environ.get('FIG_FILE', None)
self.explicit_project_name = None
def dispatch(self, *args, **kwargs): def dispatch(self, *args, **kwargs):
try: try:
super(Command, self).dispatch(*args, **kwargs) super(Command, self).dispatch(*args, **kwargs)
@ -40,60 +37,70 @@ class Command(DocoptCommand):
elif call_silently(['which', 'docker-osx']) == 0: elif call_silently(['which', 'docker-osx']) == 0:
raise errors.ConnectionErrorDockerOSX() raise errors.ConnectionErrorDockerOSX()
else: else:
raise errors.ConnectionErrorGeneric(self.client.base_url) raise errors.ConnectionErrorGeneric(self.get_client().base_url)
def perform_command(self, options, *args, **kwargs): def perform_command(self, options, handler, command_options):
if options['--file'] is not None: explicit_config_path = options.get('--file') or os.environ.get('FIG_FILE')
self.yaml_path = os.path.join(self.base_dir, options['--file']) project = self.get_project(
if options['--project-name'] is not None: self.get_config_path(explicit_config_path),
self.explicit_project_name = options['--project-name'] project_name=options.get('--project-name'),
return super(Command, self).perform_command(options, *args, **kwargs) verbose=options.get('--verbose'))
@cached_property handler(project, command_options)
def client(self):
return Client(docker_url())
@cached_property def get_client(self, verbose=False):
def project(self): client = Client(docker_url())
if verbose:
version_info = six.iteritems(client.version())
log.info("Fig version %s", __version__)
log.info("Docker base_url: %s", client.base_url)
log.info("Docker version: %s",
", ".join("%s=%s" % item for item in version_info))
return verbose_proxy.VerboseProxy('docker', client)
return client
def get_config(self, config_path):
try: try:
config = yaml.safe_load(open(self.yaml_path)) with open(config_path, 'r') as fh:
return yaml.safe_load(fh)
except IOError as e: except IOError as e:
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
raise errors.FigFileNotFound(os.path.basename(e.filename)) raise errors.FigFileNotFound(os.path.basename(e.filename))
raise errors.UserError(six.text_type(e)) raise errors.UserError(six.text_type(e))
def get_project(self, config_path, project_name=None, verbose=False):
try: try:
return Project.from_config(self.project_name, config, self.client) return Project.from_config(
self.get_project_name(config_path, project_name),
self.get_config(config_path),
self.get_client(verbose=verbose))
except ConfigError as e: except ConfigError as e:
raise errors.UserError(six.text_type(e)) raise errors.UserError(six.text_type(e))
@cached_property def get_project_name(self, config_path, project_name=None):
def project_name(self): def normalize_name(name):
project = os.path.basename(os.path.dirname(os.path.abspath(self.yaml_path))) return re.sub(r'[^a-zA-Z0-9]', '', name)
if self.explicit_project_name is not None:
project = self.explicit_project_name
project = re.sub(r'[^a-zA-Z0-9]', '', project)
if not project:
project = 'default'
return project
@cached_property if project_name is not None:
def formatter(self): return normalize_name(project_name)
return Formatter()
@cached_property project = os.path.basename(os.path.dirname(os.path.abspath(config_path)))
def yaml_path(self): if project:
if self._yaml_path is not None: return normalize_name(project)
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'") return 'default'
log.warning("Please be aware that fig.yml the expected extension in most cases, and using .yaml can cause compatibility issues in future")
def get_config_path(self, file_path=None):
if file_path:
return os.path.join(self.base_dir, file_path)
if os.path.exists(os.path.join(self.base_dir, 'fig.yaml')):
log.warning("Fig just read the file 'fig.yaml' on startup, rather "
"than 'fig.yml'")
log.warning("Please be aware that fig.yml the expected extension "
"in most cases, and using .yaml can cause compatibility "
"issues in future")
return os.path.join(self.base_dir, 'fig.yaml') return os.path.join(self.base_dir, 'fig.yaml')
else:
return os.path.join(self.base_dir, 'fig.yml')
@yaml_path.setter return os.path.join(self.base_dir, 'fig.yml')
def yaml_path(self, value):
self._yaml_path = value

View File

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

View File

@ -98,7 +98,7 @@ class TopLevelCommand(Command):
options['version'] = "fig %s" % __version__ options['version'] = "fig %s" % __version__
return options return options
def build(self, options): def build(self, project, options):
""" """
Build or rebuild services. Build or rebuild services.
@ -112,9 +112,9 @@ class TopLevelCommand(Command):
--no-cache Do not use cache when building the image. --no-cache Do not use cache when building the image.
""" """
no_cache = bool(options.get('--no-cache', False)) no_cache = bool(options.get('--no-cache', False))
self.project.build(service_names=options['SERVICE'], no_cache=no_cache) project.build(service_names=options['SERVICE'], no_cache=no_cache)
def help(self, options): def help(self, project, options):
""" """
Get help on a command. Get help on a command.
@ -125,15 +125,15 @@ class TopLevelCommand(Command):
raise NoSuchCommand(command, self) raise NoSuchCommand(command, self)
raise SystemExit(getdoc(getattr(self, command))) raise SystemExit(getdoc(getattr(self, command)))
def kill(self, options): def kill(self, project, options):
""" """
Force stop service containers. Force stop service containers.
Usage: kill [SERVICE...] Usage: kill [SERVICE...]
""" """
self.project.kill(service_names=options['SERVICE']) project.kill(service_names=options['SERVICE'])
def logs(self, options): def logs(self, project, options):
""" """
View output from containers. View output from containers.
@ -142,14 +142,13 @@ class TopLevelCommand(Command):
Options: Options:
--no-color Produce monochrome output. --no-color Produce monochrome output.
""" """
containers = self.project.containers(service_names=options['SERVICE'], stopped=True) containers = project.containers(service_names=options['SERVICE'], stopped=True)
monochrome = options['--no-color'] monochrome = options['--no-color']
print("Attaching to", list_containers(containers)) print("Attaching to", list_containers(containers))
LogPrinter(containers, attach_params={'logs': True}, monochrome=monochrome).run() LogPrinter(containers, attach_params={'logs': True}, monochrome=monochrome).run()
def ps(self, options): def ps(self, project, options):
""" """
List containers. List containers.
@ -158,7 +157,7 @@ class TopLevelCommand(Command):
Options: Options:
-q Only display IDs -q Only display IDs
""" """
containers = self.project.containers(service_names=options['SERVICE'], stopped=True) + self.project.containers(service_names=options['SERVICE'], one_off=True) containers = project.containers(service_names=options['SERVICE'], stopped=True) + project.containers(service_names=options['SERVICE'], one_off=True)
if options['-q']: if options['-q']:
for container in containers: for container in containers:
@ -183,7 +182,7 @@ class TopLevelCommand(Command):
]) ])
print(Formatter().table(headers, rows)) print(Formatter().table(headers, rows))
def rm(self, options): def rm(self, project, options):
""" """
Remove stopped service containers. Remove stopped service containers.
@ -193,21 +192,21 @@ class TopLevelCommand(Command):
--force Don't ask to confirm removal --force Don't ask to confirm removal
-v Remove volumes associated with containers -v Remove volumes associated with containers
""" """
all_containers = self.project.containers(service_names=options['SERVICE'], stopped=True) all_containers = project.containers(service_names=options['SERVICE'], stopped=True)
stopped_containers = [c for c in all_containers if not c.is_running] stopped_containers = [c for c in all_containers if not c.is_running]
if len(stopped_containers) > 0: if len(stopped_containers) > 0:
print("Going to remove", list_containers(stopped_containers)) print("Going to remove", list_containers(stopped_containers))
if options.get('--force') \ if options.get('--force') \
or yesno("Are you sure? [yN] ", default=False): or yesno("Are you sure? [yN] ", default=False):
self.project.remove_stopped( project.remove_stopped(
service_names=options['SERVICE'], service_names=options['SERVICE'],
v=options.get('-v', False) v=options.get('-v', False)
) )
else: else:
print("No stopped containers") print("No stopped containers")
def run(self, options): def run(self, project, options):
""" """
Run a one-off command on a service. Run a one-off command on a service.
@ -229,14 +228,13 @@ class TopLevelCommand(Command):
--rm Remove container after run. Ignored in detached mode. --rm Remove container after run. Ignored in detached mode.
--no-deps Don't start linked services. --no-deps Don't start linked services.
""" """
service = project.get_service(options['SERVICE'])
service = self.project.get_service(options['SERVICE'])
if not options['--no-deps']: if not options['--no-deps']:
deps = service.get_linked_names() deps = service.get_linked_names()
if len(deps) > 0: if len(deps) > 0:
self.project.up( project.up(
service_names=deps, service_names=deps,
start_links=True, start_links=True,
recreate=False, recreate=False,
@ -262,14 +260,14 @@ class TopLevelCommand(Command):
print(container.name) print(container.name)
else: else:
service.start_container(container, ports=None, one_off=True) service.start_container(container, ports=None, one_off=True)
dockerpty.start(self.client, container.id) dockerpty.start(project.client, container.id)
exit_code = container.wait() exit_code = container.wait()
if options['--rm']: if options['--rm']:
log.info("Removing %s..." % container.name) log.info("Removing %s..." % container.name)
self.client.remove_container(container.id) project.client.remove_container(container.id)
sys.exit(exit_code) sys.exit(exit_code)
def scale(self, options): def scale(self, project, options):
""" """
Set number of containers to run for a service. Set number of containers to run for a service.
@ -290,19 +288,24 @@ class TopLevelCommand(Command):
raise UserError('Number of containers for service "%s" is not a ' raise UserError('Number of containers for service "%s" is not a '
'number' % service_name) 'number' % service_name)
try: try:
self.project.get_service(service_name).scale(num) project.get_service(service_name).scale(num)
except CannotBeScaledError: 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) 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): def start(self, project, options):
""" """
Start existing containers. Start existing containers.
Usage: start [SERVICE...] Usage: start [SERVICE...]
""" """
self.project.start(service_names=options['SERVICE']) project.start(service_names=options['SERVICE'])
def stop(self, options): def stop(self, project, options):
""" """
Stop running containers without removing them. Stop running containers without removing them.
@ -310,9 +313,9 @@ class TopLevelCommand(Command):
Usage: stop [SERVICE...] Usage: stop [SERVICE...]
""" """
self.project.stop(service_names=options['SERVICE']) project.stop(service_names=options['SERVICE'])
def up(self, options): def up(self, project, options):
""" """
Build, (re)create, start and attach to containers for a service. Build, (re)create, start and attach to containers for a service.
@ -343,13 +346,13 @@ class TopLevelCommand(Command):
recreate = not options['--no-recreate'] recreate = not options['--no-recreate']
service_names = options['SERVICE'] service_names = options['SERVICE']
self.project.up( project.up(
service_names=service_names, service_names=service_names,
start_links=start_links, start_links=start_links,
recreate=recreate recreate=recreate
) )
to_attach = [c for s in self.project.get_services(service_names) for c in s.containers()] to_attach = [c for s in project.get_services(service_names) for c in s.containers()]
if not detached: if not detached:
print("Attaching to", list_containers(to_attach)) print("Attaching to", list_containers(to_attach))
@ -359,12 +362,12 @@ class TopLevelCommand(Command):
log_printer.run() log_printer.run()
finally: finally:
def handler(signal, frame): def handler(signal, frame):
self.project.kill(service_names=service_names) project.kill(service_names=service_names)
sys.exit(0) sys.exit(0)
signal.signal(signal.SIGINT, handler) signal.signal(signal.SIGINT, handler)
print("Gracefully stopping... (press Ctrl+C again to force)") print("Gracefully stopping... (press Ctrl+C again to force)")
self.project.stop(service_names=service_names) project.stop(service_names=service_names)
def list_containers(containers): def list_containers(containers):

View File

@ -7,25 +7,6 @@ import subprocess
import platform import platform
def cached_property(f):
"""
returns a cached property that is calculated by function f
http://code.activestate.com/recipes/576563-cached-property/
"""
def get(self):
try:
return self._property_cache[f]
except AttributeError:
self._property_cache = {}
x = self._property_cache[f] = f(self)
return x
except KeyError:
x = self._property_cache[f] = f(self)
return x
return property(get)
def yesno(prompt, default=None): def yesno(prompt, default=None):
""" """
Prompt the user for a yes or no. Prompt the user for a yes or no.

58
fig/cli/verbose_proxy.py Normal file
View File

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

View File

@ -5,6 +5,7 @@ from fig.cli.main import TopLevelCommand
from fig.packages.six import StringIO from fig.packages.six import StringIO
import sys import sys
class CLITestCase(DockerClientTestCase): class CLITestCase(DockerClientTestCase):
def setUp(self): def setUp(self):
super(CLITestCase, self).setUp() super(CLITestCase, self).setUp()
@ -15,12 +16,16 @@ class CLITestCase(DockerClientTestCase):
def tearDown(self): def tearDown(self):
sys.exit = self.old_sys_exit sys.exit = self.old_sys_exit
self.command.project.kill() self.project.kill()
self.command.project.remove_stopped() self.project.remove_stopped()
@property
def project(self):
return self.command.get_project(self.command.get_config_path())
@patch('sys.stdout', new_callable=StringIO) @patch('sys.stdout', new_callable=StringIO)
def test_ps(self, mock_stdout): def test_ps(self, mock_stdout):
self.command.project.get_service('simple').create_container() self.project.get_service('simple').create_container()
self.command.dispatch(['ps'], None) self.command.dispatch(['ps'], None)
self.assertIn('simplefigfile_simple_1', mock_stdout.getvalue()) self.assertIn('simplefigfile_simple_1', mock_stdout.getvalue())
@ -64,17 +69,17 @@ class CLITestCase(DockerClientTestCase):
def test_up(self): def test_up(self):
self.command.dispatch(['up', '-d'], None) self.command.dispatch(['up', '-d'], None)
service = self.command.project.get_service('simple') service = self.project.get_service('simple')
another = self.command.project.get_service('another') another = self.project.get_service('another')
self.assertEqual(len(service.containers()), 1) self.assertEqual(len(service.containers()), 1)
self.assertEqual(len(another.containers()), 1) self.assertEqual(len(another.containers()), 1)
def test_up_with_links(self): def test_up_with_links(self):
self.command.base_dir = 'tests/fixtures/links-figfile' self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['up', '-d', 'web'], None) self.command.dispatch(['up', '-d', 'web'], None)
web = self.command.project.get_service('web') web = self.project.get_service('web')
db = self.command.project.get_service('db') db = self.project.get_service('db')
console = self.command.project.get_service('console') console = self.project.get_service('console')
self.assertEqual(len(web.containers()), 1) self.assertEqual(len(web.containers()), 1)
self.assertEqual(len(db.containers()), 1) self.assertEqual(len(db.containers()), 1)
self.assertEqual(len(console.containers()), 0) self.assertEqual(len(console.containers()), 0)
@ -82,16 +87,16 @@ class CLITestCase(DockerClientTestCase):
def test_up_with_no_deps(self): def test_up_with_no_deps(self):
self.command.base_dir = 'tests/fixtures/links-figfile' self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['up', '-d', '--no-deps', 'web'], None) self.command.dispatch(['up', '-d', '--no-deps', 'web'], None)
web = self.command.project.get_service('web') web = self.project.get_service('web')
db = self.command.project.get_service('db') db = self.project.get_service('db')
console = self.command.project.get_service('console') console = self.project.get_service('console')
self.assertEqual(len(web.containers()), 1) self.assertEqual(len(web.containers()), 1)
self.assertEqual(len(db.containers()), 0) self.assertEqual(len(db.containers()), 0)
self.assertEqual(len(console.containers()), 0) self.assertEqual(len(console.containers()), 0)
def test_up_with_recreate(self): def test_up_with_recreate(self):
self.command.dispatch(['up', '-d'], None) self.command.dispatch(['up', '-d'], None)
service = self.command.project.get_service('simple') service = self.project.get_service('simple')
self.assertEqual(len(service.containers()), 1) self.assertEqual(len(service.containers()), 1)
old_ids = [c.id for c in service.containers()] old_ids = [c.id for c in service.containers()]
@ -105,7 +110,7 @@ class CLITestCase(DockerClientTestCase):
def test_up_with_keep_old(self): def test_up_with_keep_old(self):
self.command.dispatch(['up', '-d'], None) self.command.dispatch(['up', '-d'], None)
service = self.command.project.get_service('simple') service = self.project.get_service('simple')
self.assertEqual(len(service.containers()), 1) self.assertEqual(len(service.containers()), 1)
old_ids = [c.id for c in service.containers()] old_ids = [c.id for c in service.containers()]
@ -117,19 +122,18 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(old_ids, new_ids) self.assertEqual(old_ids, new_ids)
@patch('dockerpty.start') @patch('dockerpty.start')
def test_run_service_without_links(self, mock_stdout): def test_run_service_without_links(self, mock_stdout):
self.command.base_dir = 'tests/fixtures/links-figfile' self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['run', 'console', '/bin/true'], None) self.command.dispatch(['run', 'console', '/bin/true'], None)
self.assertEqual(len(self.command.project.containers()), 0) self.assertEqual(len(self.project.containers()), 0)
@patch('dockerpty.start') @patch('dockerpty.start')
def test_run_service_with_links(self, __): def test_run_service_with_links(self, __):
self.command.base_dir = 'tests/fixtures/links-figfile' self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['run', 'web', '/bin/true'], None) self.command.dispatch(['run', 'web', '/bin/true'], None)
db = self.command.project.get_service('db') db = self.project.get_service('db')
console = self.command.project.get_service('console') console = self.project.get_service('console')
self.assertEqual(len(db.containers()), 1) self.assertEqual(len(db.containers()), 1)
self.assertEqual(len(console.containers()), 0) self.assertEqual(len(console.containers()), 0)
@ -137,14 +141,14 @@ class CLITestCase(DockerClientTestCase):
def test_run_with_no_deps(self, __): def test_run_with_no_deps(self, __):
self.command.base_dir = 'tests/fixtures/links-figfile' self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['run', '--no-deps', 'web', '/bin/true'], None) self.command.dispatch(['run', '--no-deps', 'web', '/bin/true'], None)
db = self.command.project.get_service('db') db = self.project.get_service('db')
self.assertEqual(len(db.containers()), 0) self.assertEqual(len(db.containers()), 0)
@patch('dockerpty.start') @patch('dockerpty.start')
def test_run_does_not_recreate_linked_containers(self, __): def test_run_does_not_recreate_linked_containers(self, __):
self.command.base_dir = 'tests/fixtures/links-figfile' self.command.base_dir = 'tests/fixtures/links-figfile'
self.command.dispatch(['up', '-d', 'db'], None) self.command.dispatch(['up', '-d', 'db'], None)
db = self.command.project.get_service('db') db = self.project.get_service('db')
self.assertEqual(len(db.containers()), 1) self.assertEqual(len(db.containers()), 1)
old_ids = [c.id for c in db.containers()] old_ids = [c.id for c in db.containers()]
@ -161,11 +165,11 @@ class CLITestCase(DockerClientTestCase):
self.command.base_dir = 'tests/fixtures/commands-figfile' self.command.base_dir = 'tests/fixtures/commands-figfile'
self.client.build('tests/fixtures/simple-dockerfile', tag='figtest_test') self.client.build('tests/fixtures/simple-dockerfile', tag='figtest_test')
for c in self.command.project.containers(stopped=True, one_off=True): for c in self.project.containers(stopped=True, one_off=True):
c.remove() c.remove()
self.command.dispatch(['run', 'implicit'], None) self.command.dispatch(['run', 'implicit'], None)
service = self.command.project.get_service('implicit') service = self.project.get_service('implicit')
containers = service.containers(stopped=True, one_off=True) containers = service.containers(stopped=True, one_off=True)
self.assertEqual( self.assertEqual(
[c.human_readable_command for c in containers], [c.human_readable_command for c in containers],
@ -173,7 +177,7 @@ class CLITestCase(DockerClientTestCase):
) )
self.command.dispatch(['run', 'explicit'], None) self.command.dispatch(['run', 'explicit'], None)
service = self.command.project.get_service('explicit') service = self.project.get_service('explicit')
containers = service.containers(stopped=True, one_off=True) containers = service.containers(stopped=True, one_off=True)
self.assertEqual( self.assertEqual(
[c.human_readable_command for c in containers], [c.human_readable_command for c in containers],
@ -181,7 +185,7 @@ class CLITestCase(DockerClientTestCase):
) )
def test_rm(self): def test_rm(self):
service = self.command.project.get_service('simple') service = self.project.get_service('simple')
service.create_container() service.create_container()
service.kill() service.kill()
self.assertEqual(len(service.containers(stopped=True)), 1) self.assertEqual(len(service.containers(stopped=True)), 1)
@ -189,24 +193,23 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(service.containers(stopped=True)), 0) self.assertEqual(len(service.containers(stopped=True)), 0)
def test_scale(self): def test_scale(self):
project = self.command.project project = self.project
self.command.scale({'SERVICE=NUM': ['simple=1']}) self.command.scale(project, {'SERVICE=NUM': ['simple=1']})
self.assertEqual(len(project.get_service('simple').containers()), 1) self.assertEqual(len(project.get_service('simple').containers()), 1)
self.command.scale({'SERVICE=NUM': ['simple=3', 'another=2']}) self.command.scale(project, {'SERVICE=NUM': ['simple=3', 'another=2']})
self.assertEqual(len(project.get_service('simple').containers()), 3) self.assertEqual(len(project.get_service('simple').containers()), 3)
self.assertEqual(len(project.get_service('another').containers()), 2) self.assertEqual(len(project.get_service('another').containers()), 2)
self.command.scale({'SERVICE=NUM': ['simple=1', 'another=1']}) self.command.scale(project, {'SERVICE=NUM': ['simple=1', 'another=1']})
self.assertEqual(len(project.get_service('simple').containers()), 1) self.assertEqual(len(project.get_service('simple').containers()), 1)
self.assertEqual(len(project.get_service('another').containers()), 1) self.assertEqual(len(project.get_service('another').containers()), 1)
self.command.scale({'SERVICE=NUM': ['simple=1', 'another=1']}) self.command.scale(project, {'SERVICE=NUM': ['simple=1', 'another=1']})
self.assertEqual(len(project.get_service('simple').containers()), 1) self.assertEqual(len(project.get_service('simple').containers()), 1)
self.assertEqual(len(project.get_service('another').containers()), 1) self.assertEqual(len(project.get_service('another').containers()), 1)
self.command.scale({'SERVICE=NUM': ['simple=0', 'another=0']}) self.command.scale(project, {'SERVICE=NUM': ['simple=0', 'another=0']})
self.assertEqual(len(project.get_service('simple').containers()), 0) self.assertEqual(len(project.get_service('simple').containers()), 0)
self.assertEqual(len(project.get_service('another').containers()), 0) self.assertEqual(len(project.get_service('another').containers()), 0)

View File

View File

@ -0,0 +1,30 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from tests import unittest
from fig.cli import verbose_proxy
class VerboseProxy(unittest.TestCase):
def test_format_call(self):
expected = "(u'arg1', True, key=u'value')"
actual = verbose_proxy.format_call(
("arg1", True),
{'key': 'value'})
self.assertEqual(expected, actual)
def test_format_return_sequence(self):
expected = "(list with 10 items)"
actual = verbose_proxy.format_return(list(range(10)), 2)
self.assertEqual(expected, actual)
def test_format_return(self):
expected = "{u'Id': u'ok'}"
actual = verbose_proxy.format_return({'Id': 'ok'}, 2)
self.assertEqual(expected, actual)
def test_format_return_no_result(self):
actual = verbose_proxy.format_return(None, 2)
self.assertEqual(None, actual)

View File

@ -4,6 +4,8 @@ import logging
import os import os
from .. import unittest from .. import unittest
import mock
from fig.cli import main from fig.cli import main
from fig.cli.main import TopLevelCommand from fig.cli.main import TopLevelCommand
from fig.packages.six import StringIO from fig.packages.six import StringIO
@ -16,24 +18,37 @@ class CLITestCase(unittest.TestCase):
try: try:
os.chdir('tests/fixtures/simple-figfile') os.chdir('tests/fixtures/simple-figfile')
command = TopLevelCommand() command = TopLevelCommand()
self.assertEquals('simplefigfile', command.project_name) project_name = command.get_project_name(command.get_config_path())
self.assertEquals('simplefigfile', project_name)
finally: finally:
os.chdir(cwd) os.chdir(cwd)
def test_project_name_with_explicit_base_dir(self): def test_project_name_with_explicit_base_dir(self):
command = TopLevelCommand() command = TopLevelCommand()
command.base_dir = 'tests/fixtures/simple-figfile' command.base_dir = 'tests/fixtures/simple-figfile'
self.assertEquals('simplefigfile', command.project_name) project_name = command.get_project_name(command.get_config_path())
self.assertEquals('simplefigfile', project_name)
def test_project_name_with_explicit_project_name(self): def test_project_name_with_explicit_project_name(self):
command = TopLevelCommand() command = TopLevelCommand()
command.explicit_project_name = 'explicit-project-name' name = 'explicit-project-name'
self.assertEquals('explicitprojectname', command.project_name) project_name = command.get_project_name(None, project_name=name)
self.assertEquals('explicitprojectname', project_name)
def test_yaml_filename_check(self): def test_yaml_filename_check(self):
command = TopLevelCommand() command = TopLevelCommand()
command.base_dir = 'tests/fixtures/longer-filename-figfile' command.base_dir = 'tests/fixtures/longer-filename-figfile'
self.assertTrue(command.project.get_service('definedinyamlnotyml')) with mock.patch('fig.cli.command.log', autospec=True) as mock_log:
self.assertTrue(command.get_config_path())
self.assertEqual(mock_log.warning.call_count, 2)
def test_get_project(self):
command = TopLevelCommand()
command.base_dir = 'tests/fixtures/longer-filename-figfile'
project = command.get_project(command.get_config_path())
self.assertEqual(project.name, 'longerfilenamefigfile')
self.assertTrue(project.client)
self.assertTrue(project.services)
def test_help(self): def test_help(self):
command = TopLevelCommand() command = TopLevelCommand()