Skip to content

Commit

Permalink
Extract a new command (fig tag) and remove it from (fig build)
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
  • Loading branch information
dnephin committed Sep 26, 2014
1 parent 60acb8b commit b5a3dce
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 22 deletions.
5 changes: 5 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ Start existing containers for a service.

Stop running containers without removing them. They can be started again with `fig start`.

## tag

Tag all containers that were created with `fig build` with tags from the
`fig.yml`.

## up

Build, (re)create, start and attach to containers for a service.
Expand Down
8 changes: 8 additions & 0 deletions fig/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,14 @@ def stop(self, project, options):
"""
project.stop(service_names=options['SERVICE'])

def tag(self, project, options):
"""
Tag images that were built by a build directive.
Usage: tag [SERVICE...]
"""
project.tag(service_names=options['SERVICE'])

def restart(self, project, options):
"""
Restart running containers.
Expand Down
4 changes: 4 additions & 0 deletions fig/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ def build(self, service_names=None, no_cache=False):
else:
log.info('%s uses an image, skipping' % service.name)

def tag(self, service_names=None):
for service in self.get_services(service_names):
service.tag()

def up(self, service_names=None, start_links=True, recreate=True):
running_containers = []

Expand Down
21 changes: 18 additions & 3 deletions fig/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ def _get_container_create_options(self, override_options, one_off=False):
container_options['environment'] = dict(resolve_env(k, v) for k, v in container_options['environment'].iteritems())

if self.can_be_built():
if not self.client.images(name=self.full_name):
if not self.get_image_ids():
self.build()
container_options['image'] = self.full_name

Expand All @@ -400,6 +400,17 @@ def _get_container_create_options(self, override_options, one_off=False):

return container_options

def get_image_ids(self):
images = self.client.images(name=self.full_name)
return [image['Id'] for image in images]

def get_latest_image_id(self):
images = self.get_image_ids()
if len(images) < 1:
raise BuildError(
self, 'No images for %s, build first' % self.full_name)
return images[0]

def build(self, no_cache=False):
log.info('Building %s...' % self.name)

Expand Down Expand Up @@ -427,10 +438,14 @@ def build(self, no_cache=False):
if image_id is None:
raise BuildError(self, event if all_events else 'Unknown')

self.tag_image(image_id)
return image_id

def tag_image(self, image_id):
def tag(self):
if not self.can_be_built():
log.info('%s uses an image, skipping' % self.name)
return

image_id = self.get_latest_image_id()
for tag in self.options.get('tags', []):
image_name, image_tag = split_tag(tag)
self.client.tag(image_id, image_name, tag=image_tag)
Expand Down
8 changes: 6 additions & 2 deletions tests/integration/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,22 @@ def test_build_no_cache(self, mock_stdout):
self.assertNotIn(cache_indicator, output)

@patch('sys.stdout', new_callable=StringIO)
def test_build_with_tags(self, mock_stdout):
def test_tag(self, mock_stdout):
self.command.base_dir = 'tests/fixtures/tags-figfile'
tags = self.project.get_service('simple').options['tags']

try:
self.command.dispatch(['build', 'simple'], None)
self.command.dispatch(['tag', 'simple'], None)
for tag in tags:
tag_name, _ = split_tag(tag)
self.assertTrue(self.client.images(tag_name))
finally:
for tag in tags:
self.client.remove_image(tag, force=True)
try:
self.client.remove_image(tag, force=True)
except Exception:
pass

def test_up(self):
self.command.dispatch(['up', '-d'], None)
Expand Down
57 changes: 40 additions & 17 deletions tests/unit/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,14 @@ def setUp(self):
self.mock_client = mock.create_autospec(docker.Client)

def test_build_with_build_Error(self):
mock_client = mock.create_autospec(docker.Client)
service = Service('buildtest', client=mock_client, build='/path')
service = Service('buildtest', client=self.mock_client, build='/path')
with self.assertRaises(BuildError):
service.build()

def test_build_with_cache(self):
mock_client = mock.create_autospec(docker.Client)
service = Service(
'buildtest',
client=mock_client,
client=self.mock_client,
build='/path',
tags=['foo', 'foo:v2'])
expected = 'abababab'
Expand All @@ -43,24 +41,51 @@ def test_build_with_cache(self):
]
image_id = service.build()
self.assertEqual(image_id, expected)
mock_client.build.assert_called_once_with(
self.mock_client.build.assert_called_once_with(
'/path',
tag=service.full_name,
stream=True,
rm=True,
nocache=False)

self.assertEqual(mock_client.tag.mock_calls, [
mock.call(image_id, 'foo', tag=None),
mock.call(image_id, 'foo', tag='v2'),
])

def test_bad_tags_from_config(self):
with self.assertRaises(ConfigError) as exc_context:
Service('something', tags='my_tag_is_a_string')
self.assertEqual(str(exc_context.exception),
'Service something tags must be a list.')

def test_get_image_ids(self):
service = Service('imagetest', client=self.mock_client, build='/path')
image_id = "abcd"
self.mock_client.images.return_value = [dict(Id=image_id)]
self.assertEqual(service.get_image_ids(), [image_id])

def test_tag_no_image(self):
self.mock_client.images.return_value = []
service = Service(
'tagtest',
client=self.mock_client,
build='/path',
tags=['foo', 'foo:v2'])

with self.assertRaises(BuildError):
service.tag()

def test_tag(self):
image_id = 'aaaaaa'
self.mock_client.images.return_value = [dict(Id=image_id)]
service = Service(
'tagtest',
client=self.mock_client,
build='/path',
tags=['foo', 'foo:v2'])

service.tag()
self.assertEqual(self.mock_client.tag.mock_calls, [
mock.call(image_id, 'foo', tag=None),
mock.call(image_id, 'foo', tag='v2'),
])

def test_name_validations(self):
self.assertRaises(ConfigError, lambda: Service(name=''))

Expand Down Expand Up @@ -150,23 +175,21 @@ def test_split_domainname_weird(self):
self.assertEqual(opts['domainname'], 'domain.tld', 'domainname')

def test_get_container_not_found(self):
mock_client = mock.create_autospec(docker.Client)
mock_client.containers.return_value = []
service = Service('foo', client=mock_client)
self.mock_client.containers.return_value = []
service = Service('foo', client=self.mock_client)

self.assertRaises(ValueError, service.get_container)

@mock.patch('fig.service.Container', autospec=True)
def test_get_container(self, mock_container_class):
mock_client = mock.create_autospec(docker.Client)
container_dict = dict(Name='default_foo_2')
mock_client.containers.return_value = [container_dict]
service = Service('foo', client=mock_client)
self.mock_client.containers.return_value = [container_dict]
service = Service('foo', client=self.mock_client)

container = service.get_container(number=2)
self.assertEqual(container, mock_container_class.from_ps.return_value)
mock_container_class.from_ps.assert_called_once_with(
mock_client, container_dict)
self.mock_client, container_dict)


class ServiceVolumesTest(unittest.TestCase):
Expand Down

0 comments on commit b5a3dce

Please sign in to comment.