Skip to content

Commit

Permalink
Resolves docker#213 - support tagging images with different names whe…
Browse files Browse the repository at this point in the history
…n they are built.

Signed-off-by: Daniel Nephin <dnephin@gmail.com>
  • Loading branch information
dnephin committed Aug 30, 2014
1 parent 4ccbf7e commit ae09917
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 3 deletions.
12 changes: 12 additions & 0 deletions docs/yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ dns:
- 9.9.9.9
```

### tags

Tag the image with additional names when it is built.

```
tags:
- foo
- user/service_foo
- user/service_foo:v2.3
```


### 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.
Expand Down
16 changes: 14 additions & 2 deletions fig/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ def __init__(self, name, client=None, project='default', links=None, volumes_fro
if 'image' in options and 'build' in options:
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', 'expose']
if 'tags' in options and not isinstance(options['tags'], list):
raise ConfigError("Service %s tags must be a list." % name)

supported_options = DOCKER_CONFIG_KEYS + ['build', 'expose', 'tags']

for k in options:
if k not in supported_options:
Expand Down Expand Up @@ -410,10 +413,19 @@ def build(self, no_cache=False):
image_id = match.group(1)

if image_id is None:
raise BuildError(self)
raise BuildError(self, event if all_events else 'Unknown')

self.tag_image(image_id)
return image_id

def tag_image(self, image_id):
for tag in self.options.get('tags', []):
if ':' in tag:
image_name, image_tag = tag.rsplit(':', 1)
else:
image_name, image_tag = tag, None
self.client.tag(image_id, image_name, tag=image_tag)

def can_be_built(self):
return 'build' in self.options

Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/tags-figfile/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM busybox:latest
10 changes: 10 additions & 0 deletions tests/fixtures/tags-figfile/fig.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

simple:
build: tests/fixtures/tags-figfile
command: /bin/sleep 300
tags:
- 'tag-without-version'
- 'tag-with-version:v3'
- 'user/tag-with-user'
- 'user/tag-with-user-and-version:v4'

13 changes: 13 additions & 0 deletions tests/integration/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ def test_build_no_cache(self, mock_stdout):
output = mock_stdout.getvalue()
self.assertNotIn(cache_indicator, output)

@patch('sys.stdout', new_callable=StringIO)
def test_build_with_tags(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)
for tag in tags:
self.assertTrue(self.client.images(tag))
finally:
for tag in tags:
self.client.remove_image(tag, force=True)

def test_up(self):
self.command.dispatch(['up', '-d'], None)
service = self.project.get_service('simple')
Expand Down
51 changes: 50 additions & 1 deletion tests/unit/service_test.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,59 @@
from __future__ import unicode_literals
from __future__ import absolute_import

from .. import unittest
import mock
from fig.packages import docker

from fig import Service
from fig.service import ConfigError, split_port
from fig.service import (
BuildError,
ConfigError,
split_port,
)


class ServiceTest(unittest.TestCase):

def test_build_with_build_Error(self):
mock_client = mock.create_autospec(docker.Client)
service = Service('buildtest', client=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,
build='/path',
tags=['foo', 'foo:v2'])
expected = 'abababab'

with mock.patch('fig.service.stream_output') as mock_stream_output:
mock_stream_output.return_value = [
dict(stream='Successfully built %s' % expected)
]
image_id = service.build()
self.assertEqual(image_id, expected)
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_name_validations(self):
self.assertRaises(ConfigError, lambda: Service(name=''))

Expand Down

0 comments on commit ae09917

Please sign in to comment.