Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 83 additions & 24 deletions compose/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .. import __version__
from ..project import NoSuchService, ConfigurationError
from ..service import BuildError, CannotBeScaledError
from ..config import parse_environment
from ..config import parse_environment, load
from .command import Command
from .docopt_command import NoSuchCommand
from .errors import UserError
Expand Down Expand Up @@ -270,19 +270,29 @@ def run(self, project, options):
Usage: run [options] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]

Options:
--allow-insecure-ssl Allow insecure connections to the docker
registry
-d Detached mode: Run container in the background, print
new container name.
--entrypoint CMD Override the entrypoint of the image.
-e KEY=VAL Set an environment variable (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.
--service-ports Run command with the service's ports enabled and mapped
to the host.
-T Disable pseudo-tty allocation. By default `docker-compose run`
allocates a TTY.
--allow-insecure-ssl Allow insecure connections to the docker
registry
-d Detached mode: Run container in the background,
print new container name.
--entrypoint CMD Override the entrypoint of the image.
-e KEY=VAL Set an environment variable (can be used multiple
times)
--labels-file FILE Uses all labels, in the given yaml FILE for the
SERVICE to be run.
--no-deps Don't start linked services.
--no-prefix-labels Don't prefix '.cfg' and '.cmd' when --labels-file
is used.
--remove-labels [REGEX] Removes all labels. If REGEX is given, removes
labels that match the given REGular EXpression.
(Note: This option has less priority than the
given --labels-file)
--rm Remove container after run. Ignored in detached
mode.
--service-ports Run command with the service's ports enabled and
mapped to the host.
-T Disable pseudo-tty allocation. By default
`docker-compose run` allocates a TTY.
-u, --user="" Run as specified username or uid.
"""
service = project.get_service(options['SERVICE'])

Expand Down Expand Up @@ -314,8 +324,42 @@ def run(self, project, options):
'tty': tty,
'stdin_open': not options['-d'],
'detach': options['-d'],
'labels': dict(),
}

if options['--remove-labels']:
service_cfg_lbls = service.options.get('labels')
if service_cfg_lbls is not None:
regex = options.get('--remove-labels')
a = re.compile(regex)
service.options['labels'] = dict(
(k, v) for (k,v) in service_cfg_lbls.iteritems()
if not a.match(k))

if options['--labels-file']:
# Merge labels from config with labels from --labels-file
label_cfg_file = self.get_config_path(options.get('--labels-file'))
label_list_cfg_file = load(label_cfg_file)
service_cmd_opts = [a_serv_lbl for a_serv_lbl in label_list_cfg_file
if a_serv_lbl.get('name') == service.name
and a_serv_lbl.get('labels') is not None]
if len(service_cmd_opts) == 0:
raise UserError('No labels defined for service \'' + service.name +
'\' in ' + options.get('--labels-file'))
service_cmd_lbls = service_cmd_opts[0]['labels']
service_cfg_lbls = service.options.get('labels')
prefix_cmd = ""
prefix_cfg = ""
if not options['--no-prefix-labels']:
prefix_cmd = ".cmd:"
prefix_cfg = ".cfg:"

if service_cfg_lbls is not None:
container_options['labels'].update(
(prefix_cfg + key, service_cfg_lbls[key]) for key in service_cfg_lbls)
container_options['labels'].update(
(prefix_cmd + key, service_cmd_lbls[key]) for key in service_cmd_lbls)

if options['-e']:
# Merge environment from config with -e command line
container_options['environment'] = dict(
Expand Down Expand Up @@ -434,23 +478,35 @@ def up(self, project, options):
Usage: up [options] [SERVICE...]

Options:
--allow-insecure-ssl Allow insecure connections to the docker
registry
-d Detached mode: Run containers in the background,
print new container names.
--no-color Produce monochrome output.
--no-deps Don't start linked services.
--no-recreate If containers already exist, don't recreate them.
--no-build Don't build an image, even if it's missing
-t, --timeout TIMEOUT When attached, use this timeout in seconds
for the shutdown. (default: 10)
--allow-insecure-ssl Allow insecure connections to the docker registry.
-d Detached mode: Run containers in the background,
print new container names.
--labels-file FILE Uses all labels, in the given yaml FILE for the
SERVICEs to be up.
--no-build Don't build an image, even if it's missing.
--no-deps Don't start linked services.
--no-color Produce monochrome output.
--no-prefix-labels Don't prefix '.cfg' and '.cmd' when --labels-file
is used.
--no-recreate If containers already exist, don't recreate them.
--remove-labels [REGEX] Removes all labels. If REGEX is given, removes
labels that match the given REGular EXpression.
(Note: This option has less priority than the
given --labels-file).
-t, --timeout TIMEOUT When attached, use this timeout in seconds for the
shutdown. (default: 10)

"""
insecure_registry = options['--allow-insecure-ssl']
detached = options['-d']

monochrome = options['--no-color']

if options['--labels-file']:
labels_file = self.get_config_path(options['--labels-file'])
else:
labels_file = ''

start_deps = not options['--no-deps']
recreate = not options['--no-recreate']
service_names = options['SERVICE']
Expand All @@ -462,6 +518,9 @@ def up(self, project, options):
insecure_registry=insecure_registry,
detach=detached,
do_build=not options['--no-build'],
prefix_labels=not options['--no-prefix-labels'],
remove_labels=str(options['--remove-labels']),
labels_file=labels_file
)

to_attach = [c for s in project.get_services(service_names) for c in s.containers()]
Expand Down
3 changes: 1 addition & 2 deletions compose/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
'environment',
'hostname',
'image',
'labels',
'links',
'mem_limit',
'net',
Expand Down Expand Up @@ -138,7 +139,6 @@ def resolve_environment(service_dict, working_dir=None):
service_dict['environment'] = env
return service_dict


def parse_environment(environment):
if not environment:
return {}
Expand All @@ -154,7 +154,6 @@ def parse_environment(environment):
environment
)


def split_env(env):
if '=' in env:
return env.split('=', 1)
Expand Down
16 changes: 13 additions & 3 deletions compose/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,20 +209,30 @@ def up(self,
recreate=True,
insecure_registry=False,
detach=False,
do_build=True):
do_build=True,
prefix_labels=False,
remove_labels='',
labels_file=None
):
running_containers = []
for service in self.get_services(service_names, include_deps=start_deps):
if recreate:
for (_, container) in service.recreate_containers(
insecure_registry=insecure_registry,
detach=detach,
do_build=do_build):
do_build=do_build,
prefix_labels=prefix_labels,
remove_labels=remove_labels,
labels_file=labels_file):
running_containers.append(container)
else:
for container in service.start_or_create_containers(
insecure_registry=insecure_registry,
detach=detach,
do_build=do_build):
do_build=do_build,
prefix_labels=prefix_labels,
remove_labels=remove_labels,
labels_file=labels_file):
running_containers.append(container)

return running_containers
Expand Down
57 changes: 53 additions & 4 deletions compose/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from docker.errors import APIError

from .config import DOCKER_CONFIG_KEYS
from .config import DOCKER_CONFIG_KEYS, load
from .container import Container, get_container_name
from .progress_stream import stream_output, StreamOutputError

Expand All @@ -29,6 +29,7 @@

VALID_NAME_CHARS = '[a-zA-Z0-9]'

LABEL_PREFIX = ''

class BuildError(Exception):
def __init__(self, service, reason):
Expand All @@ -50,6 +51,9 @@ class ConfigError(ValueError):
ServiceName = namedtuple('ServiceName', 'project service number')


LabelSpec = namedtuple('LabelSpec', 'key value')


class Service(object):
def __init__(self, name, client=None, project='default', links=None, external_links=None, volumes_from=None, net=None, **options):
if not re.match('^%s+$' % VALID_NAME_CHARS, name):
Expand Down Expand Up @@ -181,7 +185,6 @@ def create_container(self,
self.can_be_built() and
not self.client.images(name=self.full_name)):
self.build()

try:
return Container.create(self.client, **container_options)
except APIError as e:
Expand Down Expand Up @@ -296,7 +299,8 @@ def start_or_create_containers(
self,
insecure_registry=False,
detach=False,
do_build=True):
do_build=True,
**override_options):
containers = self.containers(stopped=True)

if not containers:
Expand All @@ -305,6 +309,7 @@ def start_or_create_containers(
insecure_registry=insecure_registry,
detach=detach,
do_build=do_build,
**override_options
)
return [self.start_container(new_container)]
else:
Expand Down Expand Up @@ -399,6 +404,51 @@ def _get_container_create_options(self, override_options, one_off=False):
self.containers(stopped=True, one_off=one_off),
one_off)

if 'labels' in container_options:
container_options['labels'] = dict(
(LABEL_PREFIX + key, container_options['labels'][key])
for key in container_options['labels'])

if 'remove_labels' in container_options:
service_cfg_lbls = container_options.get('labels')
if service_cfg_lbls is not None:
regex = container_options.get('remove_labels')
a = re.compile(regex)
container_options['labels'] = dict(
(k, v) for (k,v) in service_cfg_lbls.iteritems()
if not a.match(k))
del container_options['remove_labels']

if 'labels_file' in container_options:
# Merge labels from config with labels from --labels-file
label_cfg_file = container_options.get('labels_file')
label_list_cfg_file = load(label_cfg_file)
service_cmd_opts = [a_serv_lbl for a_serv_lbl in label_list_cfg_file
if a_serv_lbl.get('name') == self.name
and a_serv_lbl.get('labels') is not None]
if len(service_cmd_opts) == 0:
raise ConfigError ('No labels defined for service \'' +
self.name + '\' in ' +
container_options.get('labels_file'))
service_cmd_lbls = service_cmd_opts[0]['labels']
service_cfg_lbls = container_options.get('labels')
prefix_cmd = ""
prefix_cfg = ""
if 'prefix_labels' in container_options:
prefix_cmd = ".cmd:"
prefix_cfg = ".cfg:"
del container_options['prefix_labels']
temp_labels = dict()
if service_cfg_lbls is not None:
temp_labels.update(
(prefix_cfg + key, service_cfg_lbls[key]) for key in service_cfg_lbls)
temp_labels.update(
(prefix_cmd + key, service_cmd_lbls[key]) for key in service_cmd_lbls)

container_options['labels'] = temp_labels
del container_options['labels_file']


# If a qualified hostname was given, split it into an
# unqualified hostname and a domainname unless domainname
# was also given explicitly. This matches the behavior of
Expand Down Expand Up @@ -553,7 +603,6 @@ def parse_volume_spec(volume_config):

return VolumeSpec(external, internal, mode)


def parse_repository_tag(s):
if ":" not in s:
return s, ""
Expand Down