Implement 'labels' option

Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
This commit is contained in:
Aanand Prasad 2015-03-19 16:10:27 -07:00
parent da71e01d30
commit 2e6bc078fb
10 changed files with 136 additions and 7 deletions

View File

@ -32,4 +32,4 @@ def docker_client():
)
timeout = int(os.environ.get('DOCKER_CLIENT_TIMEOUT', 60))
return Client(base_url=base_url, tls=tls_config, version='1.17', timeout=timeout)
return Client(base_url=base_url, tls=tls_config, version='1.18', timeout=timeout)

View File

@ -18,6 +18,7 @@ DOCKER_CONFIG_KEYS = [
'extra_hosts',
'hostname',
'image',
'labels',
'links',
'mem_limit',
'net',
@ -180,6 +181,9 @@ def process_container_options(service_dict, working_dir=None):
if 'build' in service_dict:
service_dict['build'] = resolve_build_path(service_dict['build'], working_dir=working_dir)
if 'labels' in service_dict:
service_dict['labels'] = parse_labels(service_dict['labels'])
return service_dict
@ -198,6 +202,12 @@ def merge_service_dicts(base, override):
override.get('volumes'),
)
if 'labels' in base or 'labels' in override:
d['labels'] = merge_labels(
base.get('labels'),
override.get('labels'),
)
if 'image' in override and 'build' in d:
del d['build']
@ -216,7 +226,7 @@ def merge_service_dicts(base, override):
if key in base or key in override:
d[key] = to_list(base.get(key)) + to_list(override.get(key))
already_merged_keys = ['environment', 'volumes'] + list_keys + list_or_string_keys
already_merged_keys = ['environment', 'volumes', 'labels'] + list_keys + list_or_string_keys
for k in set(ALLOWED_KEYS) - set(already_merged_keys):
if k in override:
@ -385,6 +395,35 @@ def join_volume(pair):
return ":".join((host, container))
def merge_labels(base, override):
labels = parse_labels(base)
labels.update(parse_labels(override))
return labels
def parse_labels(labels):
if not labels:
return {}
if isinstance(labels, list):
return dict(split_label(e) for e in labels)
if isinstance(labels, dict):
return labels
raise ConfigurationError(
"labels \"%s\" must be a list or mapping" %
labels
)
def split_label(label):
if '=' in label:
return label.split('=', 1)
else:
return label, ''
def expand_path(working_dir, path):
return os.path.abspath(os.path.join(working_dir, path))

View File

@ -79,6 +79,10 @@ class Container(object):
return ', '.join(format_port(*item)
for item in sorted(six.iteritems(self.ports)))
@property
def labels(self):
return self.get('Config.Labels') or {}
@property
def human_readable_state(self):
if self.is_running:

View File

@ -321,8 +321,8 @@ expose:
- "5000"
```
In the case of `environment`, Compose "merges" entries together with
locally-defined values taking precedence:
In the case of `environment` and `labels`, Compose "merges" entries together
with locally-defined values taking precedence:
```yaml
# original service

View File

@ -10,7 +10,7 @@ Compose with a `curl` command.
### Install Docker
First, install Docker version 1.3 or greater:
First, install Docker version 1.6 or greater:
- [Instructions for Mac OS X](http://docs.docker.com/installation/mac/)
- [Instructions for Ubuntu](http://docs.docker.com/installation/ubuntulinux/)

View File

@ -253,6 +253,24 @@ environment variables (DEBUG) with a new value, and the other one
For more on `extends`, see the [tutorial](extends.md#example) and
[reference](extends.md#reference).
### labels
Add metadata to containers using [Docker labels](http://docs.docker.com/userguide/labels-custom-metadata/). You can use either an array or a dictionary.
It's recommended that you use reverse-DNS notation to prevent your labels from conflicting with those used by other software.
```
labels:
com.example.description: "Accounting webapp"
com.example.department: "Finance"
com.example.label-with-empty-value: ""
labels:
- "com.example.description=Accounting webapp"
- "com.example.department=Finance"
- "com.example.label-with-empty-value"
```
### net
Networking mode. Use the same values as the docker client `--net` parameter.

View File

@ -1,5 +1,5 @@
PyYAML==3.10
docker-py==1.1.0
docker-py==1.2.1
dockerpty==0.3.2
docopt==0.6.1
requests==2.6.1

View File

@ -30,7 +30,7 @@ install_requires = [
'requests >= 2.6.1, < 2.7',
'texttable >= 0.8.1, < 0.9',
'websocket-client >= 0.11.0, < 1.0',
'docker-py >= 1.1.0, < 1.2',
'docker-py >= 1.2.0, < 1.3',
'dockerpty >= 0.3.2, < 0.4',
'six >= 1.3.0, < 2',
]

View File

@ -584,3 +584,30 @@ class ServiceTest(DockerClientTestCase):
env = create_and_start_container(service).environment
for k, v in {'FILE_DEF': 'F1', 'FILE_DEF_EMPTY': '', 'ENV_DEF': 'E3', 'NO_DEF': ''}.items():
self.assertEqual(env[k], v)
def test_labels(self):
labels_dict = {
'com.example.description': "Accounting webapp",
'com.example.department': "Finance",
'com.example.label-with-empty-value': "",
}
service = self.create_service('web', labels=labels_dict)
labels = create_and_start_container(service).labels.items()
for pair in labels_dict.items():
self.assertIn(pair, labels)
labels_list = ["%s=%s" % pair for pair in labels_dict.items()]
service = self.create_service('web', labels=labels_list)
labels = create_and_start_container(service).labels.items()
for pair in labels_dict.items():
self.assertIn(pair, labels)
def test_empty_labels(self):
labels_list = ['foo', 'bar']
service = self.create_service('web', labels=labels_list)
labels = create_and_start_container(service).labels.items()
for name in labels_list:
self.assertIn((name, ''), labels)

View File

@ -185,6 +185,47 @@ class MergeStringsOrListsTest(unittest.TestCase):
self.assertEqual(set(service_dict['dns']), set(['8.8.8.8', '9.9.9.9']))
class MergeLabelsTest(unittest.TestCase):
def test_empty(self):
service_dict = config.merge_service_dicts({}, {})
self.assertNotIn('labels', service_dict)
def test_no_override(self):
service_dict = config.merge_service_dicts(
config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}),
config.make_service_dict('foo', {}),
)
self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': ''})
def test_no_base(self):
service_dict = config.merge_service_dicts(
config.make_service_dict('foo', {}),
config.make_service_dict('foo', {'labels': ['foo=2']}),
)
self.assertEqual(service_dict['labels'], {'foo': '2'})
def test_override_explicit_value(self):
service_dict = config.merge_service_dicts(
config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}),
config.make_service_dict('foo', {'labels': ['foo=2']}),
)
self.assertEqual(service_dict['labels'], {'foo': '2', 'bar': ''})
def test_add_explicit_value(self):
service_dict = config.merge_service_dicts(
config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}),
config.make_service_dict('foo', {'labels': ['bar=2']}),
)
self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': '2'})
def test_remove_explicit_value(self):
service_dict = config.merge_service_dicts(
config.make_service_dict('foo', {'labels': ['foo=1', 'bar=2']}),
config.make_service_dict('foo', {'labels': ['bar']}),
)
self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': ''})
class EnvTest(unittest.TestCase):
def test_parse_environment_as_list(self):
environment = [