Skip to content

Commit

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

Signed-off-by: Daniel Nephin <[email protected]>
  • Loading branch information
dnephin committed Sep 10, 2014
1 parent 98159c9 commit ea75424
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 2 deletions.
12 changes: 12 additions & 0 deletions docs/yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,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
20 changes: 18 additions & 2 deletions fig/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,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 @@ -419,10 +422,16 @@ 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', []):
image_name, image_tag = split_tag(tag)
self.client.tag(image_id, image_name, tag=image_tag)

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

Expand All @@ -433,6 +442,13 @@ def can_be_scaled(self):
return True


def split_tag(tag):
if ':' in tag:
return tag.rsplit(':', 1)
else:
return tag, None


NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(run_)?(\d+)$')


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'

15 changes: 15 additions & 0 deletions tests/integration/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from .testcases import DockerClientTestCase
from fig.cli.main import TopLevelCommand
from fig.service import split_tag


class CLITestCase(DockerClientTestCase):
Expand Down Expand Up @@ -69,6 +70,20 @@ 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:
tag_name, _ = split_tag(tag)
self.assertTrue(self.client.images(tag_name))
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
41 changes: 41 additions & 0 deletions tests/unit/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from fig import Service
from fig.service import (
BuildError,
ConfigError,
split_port,
parse_volume_spec,
Expand All @@ -17,6 +18,46 @@


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 ea75424

Please sign in to comment.