diff --git a/compose/cli/main.py b/compose/cli/main.py index c3e30919d..32dbf69b2 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -24,6 +24,7 @@ from ..bundle import MissingDigests from ..bundle import serialize_bundle from ..config import ConfigurationError from ..config import parse_environment +from ..config import parse_labels from ..config import resolve_build_args from ..config.environment import Environment from ..config.serialize import serialize_config @@ -720,7 +721,9 @@ class TopLevelCommand(object): running. If you do not want to start linked services, use `docker-compose run --no-deps SERVICE COMMAND [ARGS...]`. - Usage: run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...] + Usage: + run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] + SERVICE [COMMAND] [ARGS...] Options: -d Detached mode: Run container in the background, print @@ -728,6 +731,7 @@ class TopLevelCommand(object): --name NAME Assign a name to the container --entrypoint CMD Override the entrypoint of the image. -e KEY=VAL Set an environment variable (can be used multiple times) + -l, --label KEY=VAL Add or override a label (can be used multiple times) -u, --user="" Run as specified username or uid --no-deps Don't start linked services. --rm Remove container after run. Ignored in detached mode. @@ -1122,6 +1126,9 @@ def build_container_options(options, detach, command): parse_environment(options['-e']) ) + if options['--label']: + container_options['labels'] = parse_labels(options['--label']) + if options['--entrypoint']: container_options['entrypoint'] = options.get('--entrypoint') diff --git a/compose/config/__init__.py b/compose/config/__init__.py index b629edf66..e1032f3de 100644 --- a/compose/config/__init__.py +++ b/compose/config/__init__.py @@ -8,5 +8,7 @@ from .config import DOCKER_CONFIG_KEYS from .config import find from .config import load from .config import merge_environment +from .config import merge_labels from .config import parse_environment +from .config import parse_labels from .config import resolve_build_args diff --git a/compose/config/config.py b/compose/config/config.py index 4c3f93ddb..864bc7e90 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -1076,6 +1076,12 @@ def merge_environment(base, override): return env +def merge_labels(base, override): + labels = parse_labels(base) + labels.update(parse_labels(override)) + return labels + + def split_kv(kvpair): if '=' in kvpair: return kvpair.split('=', 1) diff --git a/compose/service.py b/compose/service.py index 0b6561d99..b696fd664 100644 --- a/compose/service.py +++ b/compose/service.py @@ -25,6 +25,7 @@ from . import const from . import progress_stream from .config import DOCKER_CONFIG_KEYS from .config import merge_environment +from .config import merge_labels from .config.errors import DependencyError from .config.types import ServicePort from .config.types import VolumeSpec @@ -778,6 +779,10 @@ class Service(object): self.options.get('environment'), override_options.get('environment')) + container_options['labels'] = merge_labels( + self.options.get('labels'), + override_options.get('labels')) + binds, affinity = merge_volume_bindings( container_options.get('volumes') or [], self.options.get('tmpfs') or [], diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index 1fdb27705..af0368177 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -403,14 +403,14 @@ _docker_compose_run() { __docker_compose_nospace return ;; - --entrypoint|--name|--user|-u|--volume|-v|--workdir|-w) + --entrypoint|--label|-l|--name|--user|-u|--volume|-v|--workdir|-w) return ;; esac case "$cur" in -*) - COMPREPLY=( $( compgen -W "-d --entrypoint -e --help --name --no-deps --publish -p --rm --service-ports -T --user -u --volume -v --workdir -w" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-d --entrypoint -e --help --label -l --name --no-deps --publish -p --rm --service-ports -T --user -u --volume -v --workdir -w" -- "$cur" ) ) ;; *) __docker_compose_services_all diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 8468dfbde..5987137f2 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1830,6 +1830,17 @@ class CLITestCase(DockerClientTestCase): assert 'FOO=bar' in environment assert 'BAR=baz' not in environment + def test_run_label_flag(self): + self.base_dir = 'tests/fixtures/run-labels' + name = 'service' + self.dispatch(['run', '-l', 'default', '--label', 'foo=baz', name, '/bin/true']) + service = self.project.get_service(name) + container, = service.containers(stopped=True, one_off=OneOffFilter.only) + labels = container.labels + assert labels['default'] == '' + assert labels['foo'] == 'baz' + assert labels['hello'] == 'world' + def test_rm(self): service = self.project.get_service('simple') service.create_container() diff --git a/tests/fixtures/run-labels/docker-compose.yml b/tests/fixtures/run-labels/docker-compose.yml new file mode 100644 index 000000000..e8cd50065 --- /dev/null +++ b/tests/fixtures/run-labels/docker-compose.yml @@ -0,0 +1,7 @@ +service: + image: busybox:latest + command: top + + labels: + foo: bar + hello: world diff --git a/tests/unit/cli_test.py b/tests/unit/cli_test.py index 1a324f50a..c6aa75b26 100644 --- a/tests/unit/cli_test.py +++ b/tests/unit/cli_test.py @@ -114,6 +114,7 @@ class CLITestCase(unittest.TestCase): 'SERVICE': 'service', 'COMMAND': None, '-e': [], + '--label': [], '--user': None, '--no-deps': None, '-d': False, @@ -150,6 +151,7 @@ class CLITestCase(unittest.TestCase): 'SERVICE': 'service', 'COMMAND': None, '-e': [], + '--label': [], '--user': None, '--no-deps': None, '-d': True, @@ -173,6 +175,7 @@ class CLITestCase(unittest.TestCase): 'SERVICE': 'service', 'COMMAND': None, '-e': [], + '--label': [], '--user': None, '--no-deps': None, '-d': True, @@ -205,6 +208,7 @@ class CLITestCase(unittest.TestCase): 'SERVICE': 'service', 'COMMAND': None, '-e': [], + '--label': [], '--user': None, '--no-deps': None, '-d': True,