mirror of https://github.com/docker/compose.git
Add a flag --no-ansi to remove control characters on parallel executions
Signed-off-by: Cecile Tonglet <cecile.tonglet@gmail.com>
This commit is contained in:
parent
7feb2685d2
commit
444d888720
|
@ -31,6 +31,7 @@ def project_from_options(project_dir, options):
|
||||||
get_config_path_from_options(project_dir, options, environment),
|
get_config_path_from_options(project_dir, options, environment),
|
||||||
project_name=options.get('--project-name'),
|
project_name=options.get('--project-name'),
|
||||||
verbose=options.get('--verbose'),
|
verbose=options.get('--verbose'),
|
||||||
|
noansi=options.get('--no-ansi'),
|
||||||
host=host,
|
host=host,
|
||||||
tls_config=tls_config_from_options(options),
|
tls_config=tls_config_from_options(options),
|
||||||
environment=environment,
|
environment=environment,
|
||||||
|
@ -81,7 +82,7 @@ def get_client(environment, verbose=False, version=None, tls_config=None, host=N
|
||||||
|
|
||||||
|
|
||||||
def get_project(project_dir, config_path=None, project_name=None, verbose=False,
|
def get_project(project_dir, config_path=None, project_name=None, verbose=False,
|
||||||
host=None, tls_config=None, environment=None, override_dir=None):
|
noansi=False, host=None, tls_config=None, environment=None, override_dir=None):
|
||||||
if not environment:
|
if not environment:
|
||||||
environment = Environment.from_env_file(project_dir)
|
environment = Environment.from_env_file(project_dir)
|
||||||
config_details = config.find(project_dir, config_path, environment, override_dir)
|
config_details = config.find(project_dir, config_path, environment, override_dir)
|
||||||
|
@ -100,7 +101,7 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
with errors.handle_connection_errors(client):
|
with errors.handle_connection_errors(client):
|
||||||
return Project.from_config(project_name, config_data, client)
|
return Project.from_config(project_name, config_data, client, noansi=noansi)
|
||||||
|
|
||||||
|
|
||||||
def get_project_name(working_dir, project_name=None, environment=None):
|
def get_project_name(working_dir, project_name=None, environment=None):
|
||||||
|
|
|
@ -159,6 +159,7 @@ class TopLevelCommand(object):
|
||||||
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
|
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
|
||||||
-p, --project-name NAME Specify an alternate project name (default: directory name)
|
-p, --project-name NAME Specify an alternate project name (default: directory name)
|
||||||
--verbose Show more output
|
--verbose Show more output
|
||||||
|
--no-ansi Do not print ANSI control characters
|
||||||
-v, --version Print version and exit
|
-v, --version Print version and exit
|
||||||
-H, --host HOST Daemon socket to connect to
|
-H, --host HOST Daemon socket to connect to
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ log = logging.getLogger(__name__)
|
||||||
STOP = object()
|
STOP = object()
|
||||||
|
|
||||||
|
|
||||||
def parallel_execute(objects, func, get_name, msg, get_deps=None, limit=None):
|
def parallel_execute(objects, func, get_name, msg, get_deps=None, limit=None, noansi=False):
|
||||||
"""Runs func on objects in parallel while ensuring that func is
|
"""Runs func on objects in parallel while ensuring that func is
|
||||||
ran on object only after it is ran on all its dependencies.
|
ran on object only after it is ran on all its dependencies.
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ def parallel_execute(objects, func, get_name, msg, get_deps=None, limit=None):
|
||||||
objects = list(objects)
|
objects = list(objects)
|
||||||
stream = get_output_stream(sys.stderr)
|
stream = get_output_stream(sys.stderr)
|
||||||
|
|
||||||
writer = ParallelStreamWriter(stream, msg)
|
writer = ParallelStreamWriter(stream, msg, noansi)
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
writer.add_object(get_name(obj))
|
writer.add_object(get_name(obj))
|
||||||
writer.write_initial()
|
writer.write_initial()
|
||||||
|
@ -221,11 +221,12 @@ class ParallelStreamWriter(object):
|
||||||
to jump to the correct line, and write over the line.
|
to jump to the correct line, and write over the line.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, stream, msg):
|
def __init__(self, stream, msg, noansi):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.lines = []
|
self.lines = []
|
||||||
self.width = 0
|
self.width = 0
|
||||||
|
self.noansi = noansi
|
||||||
|
|
||||||
def add_object(self, obj_index):
|
def add_object(self, obj_index):
|
||||||
self.lines.append(obj_index)
|
self.lines.append(obj_index)
|
||||||
|
@ -239,9 +240,7 @@ class ParallelStreamWriter(object):
|
||||||
width=self.width))
|
width=self.width))
|
||||||
self.stream.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def write(self, obj_index, status):
|
def _write_ansi(self, obj_index, status):
|
||||||
if self.msg is None:
|
|
||||||
return
|
|
||||||
position = self.lines.index(obj_index)
|
position = self.lines.index(obj_index)
|
||||||
diff = len(self.lines) - position
|
diff = len(self.lines) - position
|
||||||
# move up
|
# move up
|
||||||
|
@ -254,27 +253,41 @@ class ParallelStreamWriter(object):
|
||||||
self.stream.write("%c[%dB" % (27, diff))
|
self.stream.write("%c[%dB" % (27, diff))
|
||||||
self.stream.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
|
def _write_noansi(self, obj_index, status):
|
||||||
|
self.stream.write("{} {:<{width}} ... {}\r\n".format(self.msg, obj_index,
|
||||||
|
status, width=self.width))
|
||||||
|
self.stream.flush()
|
||||||
|
|
||||||
def parallel_operation(containers, operation, options, message):
|
def write(self, obj_index, status):
|
||||||
|
if self.msg is None:
|
||||||
|
return
|
||||||
|
if self.noansi:
|
||||||
|
self._write_noansi(obj_index, status)
|
||||||
|
else:
|
||||||
|
self._write_ansi(obj_index, status)
|
||||||
|
|
||||||
|
|
||||||
|
def parallel_operation(containers, operation, options, message, noansi=False):
|
||||||
parallel_execute(
|
parallel_execute(
|
||||||
containers,
|
containers,
|
||||||
operator.methodcaller(operation, **options),
|
operator.methodcaller(operation, **options),
|
||||||
operator.attrgetter('name'),
|
operator.attrgetter('name'),
|
||||||
message)
|
message,
|
||||||
|
noansi=noansi)
|
||||||
|
|
||||||
|
|
||||||
def parallel_remove(containers, options):
|
def parallel_remove(containers, options, noansi=False):
|
||||||
stopped_containers = [c for c in containers if not c.is_running]
|
stopped_containers = [c for c in containers if not c.is_running]
|
||||||
parallel_operation(stopped_containers, 'remove', options, 'Removing')
|
parallel_operation(stopped_containers, 'remove', options, 'Removing', noansi=noansi)
|
||||||
|
|
||||||
|
|
||||||
def parallel_pause(containers, options):
|
def parallel_pause(containers, options, noansi=False):
|
||||||
parallel_operation(containers, 'pause', options, 'Pausing')
|
parallel_operation(containers, 'pause', options, 'Pausing', noansi=noansi)
|
||||||
|
|
||||||
|
|
||||||
def parallel_unpause(containers, options):
|
def parallel_unpause(containers, options, noansi=False):
|
||||||
parallel_operation(containers, 'unpause', options, 'Unpausing')
|
parallel_operation(containers, 'unpause', options, 'Unpausing', noansi=noansi)
|
||||||
|
|
||||||
|
|
||||||
def parallel_kill(containers, options):
|
def parallel_kill(containers, options, noansi=False):
|
||||||
parallel_operation(containers, 'kill', options, 'Killing')
|
parallel_operation(containers, 'kill', options, 'Killing', noansi=noansi)
|
||||||
|
|
|
@ -60,13 +60,15 @@ class Project(object):
|
||||||
"""
|
"""
|
||||||
A collection of services.
|
A collection of services.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, services, client, networks=None, volumes=None, config_version=None):
|
def __init__(self, name, services, client, networks=None, volumes=None, config_version=None,
|
||||||
|
noansi=False):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.services = services
|
self.services = services
|
||||||
self.client = client
|
self.client = client
|
||||||
self.volumes = volumes or ProjectVolumes({})
|
self.volumes = volumes or ProjectVolumes({})
|
||||||
self.networks = networks or ProjectNetworks({}, False)
|
self.networks = networks or ProjectNetworks({}, False)
|
||||||
self.config_version = config_version
|
self.config_version = config_version
|
||||||
|
self.noansi = noansi
|
||||||
|
|
||||||
def labels(self, one_off=OneOffFilter.exclude):
|
def labels(self, one_off=OneOffFilter.exclude):
|
||||||
labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)]
|
labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)]
|
||||||
|
@ -75,7 +77,7 @@ class Project(object):
|
||||||
return labels
|
return labels
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_config(cls, name, config_data, client):
|
def from_config(cls, name, config_data, client, noansi=False):
|
||||||
"""
|
"""
|
||||||
Construct a Project from a config.Config object.
|
Construct a Project from a config.Config object.
|
||||||
"""
|
"""
|
||||||
|
@ -86,7 +88,7 @@ class Project(object):
|
||||||
networks,
|
networks,
|
||||||
use_networking)
|
use_networking)
|
||||||
volumes = ProjectVolumes.from_config(name, config_data, client)
|
volumes = ProjectVolumes.from_config(name, config_data, client)
|
||||||
project = cls(name, [], client, project_networks, volumes, config_data.version)
|
project = cls(name, [], client, project_networks, volumes, config_data.version, noansi=noansi)
|
||||||
|
|
||||||
for service_dict in config_data.services:
|
for service_dict in config_data.services:
|
||||||
service_dict = dict(service_dict)
|
service_dict = dict(service_dict)
|
||||||
|
@ -126,6 +128,7 @@ class Project(object):
|
||||||
volumes_from=volumes_from,
|
volumes_from=volumes_from,
|
||||||
secrets=secrets,
|
secrets=secrets,
|
||||||
pid_mode=pid_mode,
|
pid_mode=pid_mode,
|
||||||
|
noansi=noansi,
|
||||||
**service_dict)
|
**service_dict)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -270,7 +273,8 @@ class Project(object):
|
||||||
start_service,
|
start_service,
|
||||||
operator.attrgetter('name'),
|
operator.attrgetter('name'),
|
||||||
'Starting',
|
'Starting',
|
||||||
get_deps)
|
get_deps,
|
||||||
|
noansi=self.noansi)
|
||||||
|
|
||||||
return containers
|
return containers
|
||||||
|
|
||||||
|
@ -288,25 +292,26 @@ class Project(object):
|
||||||
self.build_container_operation_with_timeout_func('stop', options),
|
self.build_container_operation_with_timeout_func('stop', options),
|
||||||
operator.attrgetter('name'),
|
operator.attrgetter('name'),
|
||||||
'Stopping',
|
'Stopping',
|
||||||
get_deps)
|
get_deps,
|
||||||
|
noansi=self.noansi)
|
||||||
|
|
||||||
def pause(self, service_names=None, **options):
|
def pause(self, service_names=None, **options):
|
||||||
containers = self.containers(service_names)
|
containers = self.containers(service_names)
|
||||||
parallel.parallel_pause(reversed(containers), options)
|
parallel.parallel_pause(reversed(containers), options, noansi=self.noansi)
|
||||||
return containers
|
return containers
|
||||||
|
|
||||||
def unpause(self, service_names=None, **options):
|
def unpause(self, service_names=None, **options):
|
||||||
containers = self.containers(service_names)
|
containers = self.containers(service_names)
|
||||||
parallel.parallel_unpause(containers, options)
|
parallel.parallel_unpause(containers, options, noansi=self.noansi)
|
||||||
return containers
|
return containers
|
||||||
|
|
||||||
def kill(self, service_names=None, **options):
|
def kill(self, service_names=None, **options):
|
||||||
parallel.parallel_kill(self.containers(service_names), options)
|
parallel.parallel_kill(self.containers(service_names), options, noansi=self.noansi)
|
||||||
|
|
||||||
def remove_stopped(self, service_names=None, one_off=OneOffFilter.exclude, **options):
|
def remove_stopped(self, service_names=None, one_off=OneOffFilter.exclude, **options):
|
||||||
parallel.parallel_remove(self.containers(
|
parallel.parallel_remove(self.containers(
|
||||||
service_names, stopped=True, one_off=one_off
|
service_names, stopped=True, one_off=one_off
|
||||||
), options)
|
), options, noansi=self.noansi)
|
||||||
|
|
||||||
def down(self, remove_image_type, include_volumes, remove_orphans=False):
|
def down(self, remove_image_type, include_volumes, remove_orphans=False):
|
||||||
self.stop(one_off=OneOffFilter.include)
|
self.stop(one_off=OneOffFilter.include)
|
||||||
|
@ -331,7 +336,8 @@ class Project(object):
|
||||||
containers,
|
containers,
|
||||||
self.build_container_operation_with_timeout_func('restart', options),
|
self.build_container_operation_with_timeout_func('restart', options),
|
||||||
operator.attrgetter('name'),
|
operator.attrgetter('name'),
|
||||||
'Restarting')
|
'Restarting',
|
||||||
|
noansi=self.noansi)
|
||||||
return containers
|
return containers
|
||||||
|
|
||||||
def build(self, service_names=None, no_cache=False, pull=False, force_rm=False, build_args=None):
|
def build(self, service_names=None, no_cache=False, pull=False, force_rm=False, build_args=None):
|
||||||
|
@ -447,7 +453,8 @@ class Project(object):
|
||||||
do,
|
do,
|
||||||
operator.attrgetter('name'),
|
operator.attrgetter('name'),
|
||||||
None,
|
None,
|
||||||
get_deps
|
get_deps,
|
||||||
|
noansi=self.noansi,
|
||||||
)
|
)
|
||||||
if errors:
|
if errors:
|
||||||
raise ProjectError(
|
raise ProjectError(
|
||||||
|
@ -500,7 +507,8 @@ class Project(object):
|
||||||
pull_service,
|
pull_service,
|
||||||
operator.attrgetter('name'),
|
operator.attrgetter('name'),
|
||||||
'Pulling',
|
'Pulling',
|
||||||
limit=5)
|
limit=5,
|
||||||
|
noansi=self.noansi)
|
||||||
else:
|
else:
|
||||||
for service in services:
|
for service in services:
|
||||||
service.pull(ignore_pull_failures, silent=silent)
|
service.pull(ignore_pull_failures, silent=silent)
|
||||||
|
|
|
@ -158,6 +158,7 @@ class Service(object):
|
||||||
secrets=None,
|
secrets=None,
|
||||||
scale=None,
|
scale=None,
|
||||||
pid_mode=None,
|
pid_mode=None,
|
||||||
|
noansi=False,
|
||||||
**options
|
**options
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -171,6 +172,7 @@ class Service(object):
|
||||||
self.networks = networks or {}
|
self.networks = networks or {}
|
||||||
self.secrets = secrets or []
|
self.secrets = secrets or []
|
||||||
self.scale_num = scale or 1
|
self.scale_num = scale or 1
|
||||||
|
self.noansi = noansi
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -393,6 +395,7 @@ class Service(object):
|
||||||
lambda n: create_and_start(self, n),
|
lambda n: create_and_start(self, n),
|
||||||
lambda n: self.get_container_name(n),
|
lambda n: self.get_container_name(n),
|
||||||
"Creating",
|
"Creating",
|
||||||
|
noansi=self.noansi,
|
||||||
)
|
)
|
||||||
for error in errors.values():
|
for error in errors.values():
|
||||||
raise OperationFailedError(error)
|
raise OperationFailedError(error)
|
||||||
|
@ -414,6 +417,7 @@ class Service(object):
|
||||||
recreate,
|
recreate,
|
||||||
lambda c: c.name,
|
lambda c: c.name,
|
||||||
"Recreating",
|
"Recreating",
|
||||||
|
noansi=self.noansi,
|
||||||
)
|
)
|
||||||
for error in errors.values():
|
for error in errors.values():
|
||||||
raise OperationFailedError(error)
|
raise OperationFailedError(error)
|
||||||
|
@ -434,6 +438,7 @@ class Service(object):
|
||||||
lambda c: self.start_container_if_stopped(c, attach_logs=not detached),
|
lambda c: self.start_container_if_stopped(c, attach_logs=not detached),
|
||||||
lambda c: c.name,
|
lambda c: c.name,
|
||||||
"Starting",
|
"Starting",
|
||||||
|
noansi=self.noansi,
|
||||||
)
|
)
|
||||||
|
|
||||||
for error in errors.values():
|
for error in errors.values():
|
||||||
|
@ -455,6 +460,7 @@ class Service(object):
|
||||||
stop_and_remove,
|
stop_and_remove,
|
||||||
lambda c: c.name,
|
lambda c: c.name,
|
||||||
"Stopping and removing",
|
"Stopping and removing",
|
||||||
|
noansi=self.noansi,
|
||||||
)
|
)
|
||||||
|
|
||||||
def execute_convergence_plan(self, plan, timeout=None, detached=False,
|
def execute_convergence_plan(self, plan, timeout=None, detached=False,
|
||||||
|
|
|
@ -130,3 +130,19 @@ def test_parallel_execute_alignment(capsys):
|
||||||
_, err = capsys.readouterr()
|
_, err = capsys.readouterr()
|
||||||
a, b = err.split('\n')[:2]
|
a, b = err.split('\n')[:2]
|
||||||
assert a.index('...') == b.index('...')
|
assert a.index('...') == b.index('...')
|
||||||
|
|
||||||
|
|
||||||
|
def test_parallel_execute_alignment_noansi(capsys):
|
||||||
|
results, errors = parallel_execute(
|
||||||
|
objects=["short", "a very long name"],
|
||||||
|
func=lambda x: x,
|
||||||
|
get_name=six.text_type,
|
||||||
|
msg="Aligning",
|
||||||
|
noansi=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert errors == {}
|
||||||
|
|
||||||
|
_, err = capsys.readouterr()
|
||||||
|
a, b, c, d = err.split('\n')[:4]
|
||||||
|
assert a.index('...') == b.index('...') == c.index('...') == d.index('...')
|
||||||
|
|
Loading…
Reference in New Issue