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),
|
||||
project_name=options.get('--project-name'),
|
||||
verbose=options.get('--verbose'),
|
||||
noansi=options.get('--no-ansi'),
|
||||
host=host,
|
||||
tls_config=tls_config_from_options(options),
|
||||
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,
|
||||
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:
|
||||
environment = Environment.from_env_file(project_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):
|
||||
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):
|
||||
|
|
|
@ -159,6 +159,7 @@ class TopLevelCommand(object):
|
|||
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
|
||||
-p, --project-name NAME Specify an alternate project name (default: directory name)
|
||||
--verbose Show more output
|
||||
--no-ansi Do not print ANSI control characters
|
||||
-v, --version Print version and exit
|
||||
-H, --host HOST Daemon socket to connect to
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ log = logging.getLogger(__name__)
|
|||
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
|
||||
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)
|
||||
stream = get_output_stream(sys.stderr)
|
||||
|
||||
writer = ParallelStreamWriter(stream, msg)
|
||||
writer = ParallelStreamWriter(stream, msg, noansi)
|
||||
for obj in objects:
|
||||
writer.add_object(get_name(obj))
|
||||
writer.write_initial()
|
||||
|
@ -221,11 +221,12 @@ class ParallelStreamWriter(object):
|
|||
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.msg = msg
|
||||
self.lines = []
|
||||
self.width = 0
|
||||
self.noansi = noansi
|
||||
|
||||
def add_object(self, obj_index):
|
||||
self.lines.append(obj_index)
|
||||
|
@ -239,9 +240,7 @@ class ParallelStreamWriter(object):
|
|||
width=self.width))
|
||||
self.stream.flush()
|
||||
|
||||
def write(self, obj_index, status):
|
||||
if self.msg is None:
|
||||
return
|
||||
def _write_ansi(self, obj_index, status):
|
||||
position = self.lines.index(obj_index)
|
||||
diff = len(self.lines) - position
|
||||
# move up
|
||||
|
@ -254,27 +253,41 @@ class ParallelStreamWriter(object):
|
|||
self.stream.write("%c[%dB" % (27, diff))
|
||||
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(
|
||||
containers,
|
||||
operator.methodcaller(operation, **options),
|
||||
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]
|
||||
parallel_operation(stopped_containers, 'remove', options, 'Removing')
|
||||
parallel_operation(stopped_containers, 'remove', options, 'Removing', noansi=noansi)
|
||||
|
||||
|
||||
def parallel_pause(containers, options):
|
||||
parallel_operation(containers, 'pause', options, 'Pausing')
|
||||
def parallel_pause(containers, options, noansi=False):
|
||||
parallel_operation(containers, 'pause', options, 'Pausing', noansi=noansi)
|
||||
|
||||
|
||||
def parallel_unpause(containers, options):
|
||||
parallel_operation(containers, 'unpause', options, 'Unpausing')
|
||||
def parallel_unpause(containers, options, noansi=False):
|
||||
parallel_operation(containers, 'unpause', options, 'Unpausing', noansi=noansi)
|
||||
|
||||
|
||||
def parallel_kill(containers, options):
|
||||
parallel_operation(containers, 'kill', options, 'Killing')
|
||||
def parallel_kill(containers, options, noansi=False):
|
||||
parallel_operation(containers, 'kill', options, 'Killing', noansi=noansi)
|
||||
|
|
|
@ -60,13 +60,15 @@ class Project(object):
|
|||
"""
|
||||
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.services = services
|
||||
self.client = client
|
||||
self.volumes = volumes or ProjectVolumes({})
|
||||
self.networks = networks or ProjectNetworks({}, False)
|
||||
self.config_version = config_version
|
||||
self.noansi = noansi
|
||||
|
||||
def labels(self, one_off=OneOffFilter.exclude):
|
||||
labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)]
|
||||
|
@ -75,7 +77,7 @@ class Project(object):
|
|||
return labels
|
||||
|
||||
@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.
|
||||
"""
|
||||
|
@ -86,7 +88,7 @@ class Project(object):
|
|||
networks,
|
||||
use_networking)
|
||||
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:
|
||||
service_dict = dict(service_dict)
|
||||
|
@ -126,6 +128,7 @@ class Project(object):
|
|||
volumes_from=volumes_from,
|
||||
secrets=secrets,
|
||||
pid_mode=pid_mode,
|
||||
noansi=noansi,
|
||||
**service_dict)
|
||||
)
|
||||
|
||||
|
@ -270,7 +273,8 @@ class Project(object):
|
|||
start_service,
|
||||
operator.attrgetter('name'),
|
||||
'Starting',
|
||||
get_deps)
|
||||
get_deps,
|
||||
noansi=self.noansi)
|
||||
|
||||
return containers
|
||||
|
||||
|
@ -288,25 +292,26 @@ class Project(object):
|
|||
self.build_container_operation_with_timeout_func('stop', options),
|
||||
operator.attrgetter('name'),
|
||||
'Stopping',
|
||||
get_deps)
|
||||
get_deps,
|
||||
noansi=self.noansi)
|
||||
|
||||
def pause(self, service_names=None, **options):
|
||||
containers = self.containers(service_names)
|
||||
parallel.parallel_pause(reversed(containers), options)
|
||||
parallel.parallel_pause(reversed(containers), options, noansi=self.noansi)
|
||||
return containers
|
||||
|
||||
def unpause(self, service_names=None, **options):
|
||||
containers = self.containers(service_names)
|
||||
parallel.parallel_unpause(containers, options)
|
||||
parallel.parallel_unpause(containers, options, noansi=self.noansi)
|
||||
return containers
|
||||
|
||||
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):
|
||||
parallel.parallel_remove(self.containers(
|
||||
service_names, stopped=True, one_off=one_off
|
||||
), options)
|
||||
), options, noansi=self.noansi)
|
||||
|
||||
def down(self, remove_image_type, include_volumes, remove_orphans=False):
|
||||
self.stop(one_off=OneOffFilter.include)
|
||||
|
@ -331,7 +336,8 @@ class Project(object):
|
|||
containers,
|
||||
self.build_container_operation_with_timeout_func('restart', options),
|
||||
operator.attrgetter('name'),
|
||||
'Restarting')
|
||||
'Restarting',
|
||||
noansi=self.noansi)
|
||||
return containers
|
||||
|
||||
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,
|
||||
operator.attrgetter('name'),
|
||||
None,
|
||||
get_deps
|
||||
get_deps,
|
||||
noansi=self.noansi,
|
||||
)
|
||||
if errors:
|
||||
raise ProjectError(
|
||||
|
@ -500,7 +507,8 @@ class Project(object):
|
|||
pull_service,
|
||||
operator.attrgetter('name'),
|
||||
'Pulling',
|
||||
limit=5)
|
||||
limit=5,
|
||||
noansi=self.noansi)
|
||||
else:
|
||||
for service in services:
|
||||
service.pull(ignore_pull_failures, silent=silent)
|
||||
|
|
|
@ -158,6 +158,7 @@ class Service(object):
|
|||
secrets=None,
|
||||
scale=None,
|
||||
pid_mode=None,
|
||||
noansi=False,
|
||||
**options
|
||||
):
|
||||
self.name = name
|
||||
|
@ -171,6 +172,7 @@ class Service(object):
|
|||
self.networks = networks or {}
|
||||
self.secrets = secrets or []
|
||||
self.scale_num = scale or 1
|
||||
self.noansi = noansi
|
||||
self.options = options
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -393,6 +395,7 @@ class Service(object):
|
|||
lambda n: create_and_start(self, n),
|
||||
lambda n: self.get_container_name(n),
|
||||
"Creating",
|
||||
noansi=self.noansi,
|
||||
)
|
||||
for error in errors.values():
|
||||
raise OperationFailedError(error)
|
||||
|
@ -414,6 +417,7 @@ class Service(object):
|
|||
recreate,
|
||||
lambda c: c.name,
|
||||
"Recreating",
|
||||
noansi=self.noansi,
|
||||
)
|
||||
for error in errors.values():
|
||||
raise OperationFailedError(error)
|
||||
|
@ -434,6 +438,7 @@ class Service(object):
|
|||
lambda c: self.start_container_if_stopped(c, attach_logs=not detached),
|
||||
lambda c: c.name,
|
||||
"Starting",
|
||||
noansi=self.noansi,
|
||||
)
|
||||
|
||||
for error in errors.values():
|
||||
|
@ -455,6 +460,7 @@ class Service(object):
|
|||
stop_and_remove,
|
||||
lambda c: c.name,
|
||||
"Stopping and removing",
|
||||
noansi=self.noansi,
|
||||
)
|
||||
|
||||
def execute_convergence_plan(self, plan, timeout=None, detached=False,
|
||||
|
|
|
@ -130,3 +130,19 @@ def test_parallel_execute_alignment(capsys):
|
|||
_, err = capsys.readouterr()
|
||||
a, b = err.split('\n')[:2]
|
||||
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