From 33f510b3409a55fd7a4e9050853e14cbd853d18e Mon Sep 17 00:00:00 2001 From: Jesus Tinoco Date: Sat, 11 Jun 2016 01:12:02 +0200 Subject: [PATCH 1/4] 3501 - Add a new command option (images) Signed-off-by: Jesus Rodriguez Tinoco --- compose/cli/main.py | 38 ++++++++++++++++++++++++++ contrib/completion/bash/docker-compose | 10 +++++++ tests/acceptance/cli_test.py | 27 ++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/compose/cli/main.py b/compose/cli/main.py index a7aec945e..ba053e233 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -175,6 +175,7 @@ class TopLevelCommand(object): events Receive real time events from containers exec Execute a command in a running container help Get help on a command + images List images kill Kill containers logs View output from containers pause Pause services @@ -481,6 +482,43 @@ class TopLevelCommand(object): print(getdoc(subject)) + def images(self, options): + """ + List images. + Usage: images [options] [SERVICE...] + + Options: + -q Only display IDs + """ + containers = sorted( + self.project.containers(service_names=options['SERVICE'], stopped=True) + + self.project.containers(service_names=options['SERVICE'], one_off=OneOffFilter.only), + key=attrgetter('name')) + + if options['-q']: + for container in containers: + print(str.split(str(container.image), ':')[1]) + else: + headers = [ + 'Repository', + 'Tag', + 'Image Id', + 'Size' + ] + rows = [] + for container in containers: + image_config = container.image_config + repo_tags = str.split(str(image_config['RepoTags'][0]), ':') + image_id = str.split(str(container.image), ':')[1][0:12] + size = round(int(image_config['Size'])/float(1 << 20), 1) + rows.append([ + repo_tags[0], + repo_tags[1], + image_id, + size + ]) + print(Formatter().table(headers, rows)) + def kill(self, options): """ Force stop service containers. diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index f4b9342f3..fa099eac4 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -220,6 +220,16 @@ _docker_compose_help() { COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) ) } +_docker_compose_images() { + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help -q" -- "$cur" ) ) + ;; + *) + __docker_compose_services_all + ;; + esac +} _docker_compose_kill() { case "$prev" in diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index cc7bc5dfe..26baf3377 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1986,3 +1986,30 @@ class CLITestCase(DockerClientTestCase): result = wait_on_process(proc, returncode=1) assert 'exitcodefrom_another_1 exited with code 1' in result.stdout + + def test_images(self): + self.project.get_service('simple').create_container() + result = self.dispatch(['images']) + assert 'simplecomposefile_simple' in result.stdout + + def test_images_default_composefile(self): + self.base_dir = 'tests/fixtures/multiple-composefiles' + self.dispatch(['up', '-d']) + result = self.dispatch(['images']) + + self.assertIn('multiplecomposefiles_simple', result.stdout) + self.assertIn('multiplecomposefiles_another', result.stdout) + self.assertNotIn('multiplecomposefiles_yetanother', result.stdout) + + def test_images_alternate_composefile(self): + config_path = os.path.abspath( + 'tests/fixtures/multiple-composefiles/compose2.yml') + self._project = get_project(self.base_dir, [config_path]) + + self.base_dir = 'tests/fixtures/multiple-composefiles' + self.dispatch(['-f', 'compose2.yml', 'up', '-d']) + result = self.dispatch(['-f', 'compose2.yml', 'images']) + + self.assertNotIn('multiplecomposefiles_simple', result.stdout) + self.assertNotIn('multiplecomposefiles_another', result.stdout) + self.assertIn('multiplecomposefiles_yetanother', result.stdout) From 90beeaf21c15de1c0da55eb1abc22820450c3ab8 Mon Sep 17 00:00:00 2001 From: Jesus Tinoco Date: Sat, 11 Jun 2016 02:10:52 +0200 Subject: [PATCH 2/4] 3501 - Remove a test and fix other Signed-off-by: Jesus Rodriguez Tinoco --- tests/acceptance/cli_test.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 26baf3377..ef907d541 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1990,26 +1990,11 @@ class CLITestCase(DockerClientTestCase): def test_images(self): self.project.get_service('simple').create_container() result = self.dispatch(['images']) - assert 'simplecomposefile_simple' in result.stdout + assert 'busybox' in result.stdout def test_images_default_composefile(self): self.base_dir = 'tests/fixtures/multiple-composefiles' self.dispatch(['up', '-d']) result = self.dispatch(['images']) - self.assertIn('multiplecomposefiles_simple', result.stdout) - self.assertIn('multiplecomposefiles_another', result.stdout) - self.assertNotIn('multiplecomposefiles_yetanother', result.stdout) - - def test_images_alternate_composefile(self): - config_path = os.path.abspath( - 'tests/fixtures/multiple-composefiles/compose2.yml') - self._project = get_project(self.base_dir, [config_path]) - - self.base_dir = 'tests/fixtures/multiple-composefiles' - self.dispatch(['-f', 'compose2.yml', 'up', '-d']) - result = self.dispatch(['-f', 'compose2.yml', 'images']) - - self.assertNotIn('multiplecomposefiles_simple', result.stdout) - self.assertNotIn('multiplecomposefiles_another', result.stdout) - self.assertIn('multiplecomposefiles_yetanother', result.stdout) + self.assertIn('busybox', result.stdout) From 815a3af6d21b12a6b16fa52db85eec354d27626d Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 2 Mar 2017 14:59:36 -0800 Subject: [PATCH 3/4] Fix docstring for images command Signed-off-by: Joffrey F --- compose/cli/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index ba053e233..894dd9abb 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -484,7 +484,7 @@ class TopLevelCommand(object): def images(self, options): """ - List images. + List images used by the created containers. Usage: images [options] [SERVICE...] Options: @@ -510,7 +510,7 @@ class TopLevelCommand(object): image_config = container.image_config repo_tags = str.split(str(image_config['RepoTags'][0]), ':') image_id = str.split(str(container.image), ':')[1][0:12] - size = round(int(image_config['Size'])/float(1 << 20), 1) + size = round(int(image_config['Size']) / float(1 << 20), 1) rows.append([ repo_tags[0], repo_tags[1], From 8f8678987b4496384452143b6eeec88d51b14510 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Mon, 6 Mar 2017 15:56:41 -0800 Subject: [PATCH 4/4] Improve readability of code and output for the images command Signed-off-by: Joffrey F --- compose/cli/main.py | 13 ++++++++----- compose/cli/utils.py | 13 +++++++++++++ tests/acceptance/cli_test.py | 5 ++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index 894dd9abb..98fc4e451 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -47,6 +47,7 @@ from .formatter import Formatter from .log_printer import build_log_presenters from .log_printer import LogPrinter from .utils import get_version_info +from .utils import human_readable_file_size from .utils import yesno @@ -496,10 +497,11 @@ class TopLevelCommand(object): key=attrgetter('name')) if options['-q']: - for container in containers: - print(str.split(str(container.image), ':')[1]) + for image in set(c.image for c in containers): + print(image.split(':')[1]) else: headers = [ + 'Container', 'Repository', 'Tag', 'Image Id', @@ -508,10 +510,11 @@ class TopLevelCommand(object): rows = [] for container in containers: image_config = container.image_config - repo_tags = str.split(str(image_config['RepoTags'][0]), ':') - image_id = str.split(str(container.image), ':')[1][0:12] - size = round(int(image_config['Size']) / float(1 << 20), 1) + repo_tags = image_config['RepoTags'][0].split(':') + image_id = image_config['Id'].split(':')[1][:12] + size = human_readable_file_size(image_config['Size']) rows.append([ + container.name, repo_tags[0], repo_tags[1], image_id, diff --git a/compose/cli/utils.py b/compose/cli/utils.py index 580bd1b07..4d4fc4c18 100644 --- a/compose/cli/utils.py +++ b/compose/cli/utils.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals +import math import os import platform import ssl @@ -135,3 +136,15 @@ def unquote_path(s): if s[0] == '"' and s[-1] == '"': return s[1:-1] return s + + +def human_readable_file_size(size): + suffixes = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', ] + order = int(math.log(size, 2) / 10) if size else 0 + if order >= len(suffixes): + order = len(suffixes) - 1 + + return '{0:.3g} {1}'.format( + size / float(1 << (order * 10)), + suffixes[order] + ) diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index ef907d541..bec83ba1c 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1991,10 +1991,13 @@ class CLITestCase(DockerClientTestCase): self.project.get_service('simple').create_container() result = self.dispatch(['images']) assert 'busybox' in result.stdout + assert 'simplecomposefile_simple_1' in result.stdout def test_images_default_composefile(self): self.base_dir = 'tests/fixtures/multiple-composefiles' self.dispatch(['up', '-d']) result = self.dispatch(['images']) - self.assertIn('busybox', result.stdout) + assert 'busybox' in result.stdout + assert 'multiplecomposefiles_another_1' in result.stdout + assert 'multiplecomposefiles_simple_1' in result.stdout