From 541fb6525991ae7110bf512a3313c0e13ee7a42d Mon Sep 17 00:00:00 2001 From: Fender William Date: Tue, 24 Apr 2018 07:41:55 +0200 Subject: [PATCH 1/4] Add --hash opt for config command Signed-off-by: Fender William --- .gitignore | 1 + compose/cli/main.py | 17 ++++++++++++++++- contrib/completion/bash/docker-compose | 2 +- contrib/completion/zsh/_docker-compose | 3 ++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 11266c2e3..18afd643d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ compose/GITSHA *.swp .DS_Store .cache +.idea diff --git a/compose/cli/main.py b/compose/cli/main.py index d224093cb..231767424 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -328,7 +328,8 @@ class TopLevelCommand(object): anything. --services Print the service names, one per line. --volumes Print the volume names, one per line. - + --hash="all" Print the service config hash, one per line. + Set "service1,service2" for a list of specified services. """ compose_config = get_config_from_options(self.project_dir, self.toplevel_options) @@ -350,6 +351,20 @@ class TopLevelCommand(object): print('\n'.join(volume for volume in compose_config.volumes)) return + if options['--hash'] is not None: + self.project = project_from_options('.', self.toplevel_options) + if options['--hash'] == "all": + for service in self.project.services: + print('{} {}'.format(service.name, service.config_hash)) + else: + for service_name in options['--hash'].split(','): + try: + print('{} {}'.format(service_name, + self.project.get_service(service_name).config_hash)) + except NoSuchService as s: + print('{}'.format(s)) + return + print(serialize_config(compose_config, image_digests)) def create(self, options): diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index b90af45d1..f4c42362c 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -136,7 +136,7 @@ _docker_compose_bundle() { _docker_compose_config() { - COMPREPLY=( $( compgen -W "--help --quiet -q --resolve-image-digests --services --volumes" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--help --quiet -q --resolve-image-digests --services --volumes --hash" -- "$cur" ) ) } diff --git a/contrib/completion/zsh/_docker-compose b/contrib/completion/zsh/_docker-compose index aba367706..676aa117b 100644 --- a/contrib/completion/zsh/_docker-compose +++ b/contrib/completion/zsh/_docker-compose @@ -213,7 +213,8 @@ __docker-compose_subcommand() { '(--quiet -q)'{--quiet,-q}"[Only validate the configuration, don't print anything.]" \ '--resolve-image-digests[Pin image tags to digests.]' \ '--services[Print the service names, one per line.]' \ - '--volumes[Print the volume names, one per line.]' && ret=0 + '--volumes[Print the volume names, one per line.]' \ + '--hash[Print the service config hash, one per line. Set "service1,service2" for a list of specified services.]' \ && ret=0 ;; (create) _arguments \ From 707e21183f5117f4f2b5005615a7c483958b5b3d Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Tue, 7 Aug 2018 16:45:34 -0700 Subject: [PATCH 2/4] Fix config hash consistency with unprioritized networks Signed-off-by: Joffrey F --- compose/network.py | 13 +++++++++---- tests/unit/service_test.py | 6 ++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/compose/network.py b/compose/network.py index 9751f2037..2491a5989 100644 --- a/compose/network.py +++ b/compose/network.py @@ -323,7 +323,12 @@ def get_networks(service_dict, network_definitions): 'Service "{}" uses an undefined network "{}"' .format(service_dict['name'], name)) - return OrderedDict(sorted( - networks.items(), - key=lambda t: t[1].get('priority') or 0, reverse=True - )) + if any([v.get('priority') for v in networks.values()]): + return OrderedDict(sorted( + networks.items(), + key=lambda t: t[1].get('priority') or 0, reverse=True + )) + else: + # Ensure Compose will pick a consistent primary network if no + # priority is set + return OrderedDict(sorted(networks.items(), key=lambda t: t[0])) diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index f5a35d814..791019a4e 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -701,9 +701,11 @@ class ServiceTest(unittest.TestCase): image='example.com/foo', client=self.mock_client, network_mode=NetworkMode('bridge'), - networks={'bridge': {}}, + networks={'bridge': {}, 'net2': {}}, links=[(Service('one', client=self.mock_client), 'one')], - volumes_from=[VolumeFromSpec(Service('two', client=self.mock_client), 'rw', 'service')] + volumes_from=[VolumeFromSpec(Service('two', client=self.mock_client), 'rw', 'service')], + volumes=[VolumeSpec('/ext', '/int', 'ro')], + build={'context': 'some/random/path'}, ) config_hash = service.config_hash From 861031b9b7ed83866a73b48dc1c17119cd0a708e Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Tue, 7 Aug 2018 16:47:34 -0700 Subject: [PATCH 3/4] Reduce config --hash code complexity and add test Signed-off-by: Joffrey F --- compose/cli/main.py | 20 ++++++++------------ tests/acceptance/cli_test.py | 11 +++++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index 231767424..4c18d19f7 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -328,8 +328,9 @@ class TopLevelCommand(object): anything. --services Print the service names, one per line. --volumes Print the volume names, one per line. - --hash="all" Print the service config hash, one per line. - Set "service1,service2" for a list of specified services. + --hash="*" Print the service config hash, one per line. + Set "service1,service2" for a list of specified services + or use the wildcard symbol to display all services """ compose_config = get_config_from_options(self.project_dir, self.toplevel_options) @@ -352,17 +353,12 @@ class TopLevelCommand(object): return if options['--hash'] is not None: + h = options['--hash'] self.project = project_from_options('.', self.toplevel_options) - if options['--hash'] == "all": - for service in self.project.services: - print('{} {}'.format(service.name, service.config_hash)) - else: - for service_name in options['--hash'].split(','): - try: - print('{} {}'.format(service_name, - self.project.get_service(service_name).config_hash)) - except NoSuchService as s: - print('{}'.format(s)) + services = [svc for svc in options['--hash'].split(',')] if h != '*' else None + + for service in self.project.get_services(services): + print('{} {}'.format(service.name, service.config_hash)) return print(serialize_config(compose_config, image_digests)) diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 2361a1fbf..815b92c8d 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -222,6 +222,17 @@ class CLITestCase(DockerClientTestCase): self.base_dir = 'tests/fixtures/v2-full' assert self.dispatch(['config', '--quiet']).stdout == '' + def test_config_with_hash_option(self): + self.base_dir = 'tests/fixtures/v2-full' + self.project.build() + result = self.dispatch(['config', '--hash=*']) + for service in self.project.get_services(): + assert '{} {}\n'.format(service.name, service.config_hash) in result.stdout + + svc = self.project.get_service('other') + result = self.dispatch(['config', '--hash=other']) + assert result.stdout == '{} {}\n'.format(svc.name, svc.config_hash) + def test_config_default(self): self.base_dir = 'tests/fixtures/v2-full' result = self.dispatch(['config']) From ee878aee4cddad7d652f4908ff1b1ddd5474fbbd Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Tue, 7 Aug 2018 17:32:31 -0700 Subject: [PATCH 4/4] Handle missing (not built) service image in config --hash Signed-off-by: Joffrey F --- compose/cli/main.py | 6 +++--- compose/service.py | 8 +++++++- tests/acceptance/cli_test.py | 1 - 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index 4c18d19f7..07447d671 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -356,9 +356,9 @@ class TopLevelCommand(object): h = options['--hash'] self.project = project_from_options('.', self.toplevel_options) services = [svc for svc in options['--hash'].split(',')] if h != '*' else None - - for service in self.project.get_services(services): - print('{} {}'.format(service.name, service.config_hash)) + with errors.handle_connection_errors(self.project.client): + for service in self.project.get_services(services): + print('{} {}'.format(service.name, service.config_hash)) return print(serialize_config(compose_config, image_digests)) diff --git a/compose/service.py b/compose/service.py index e77780fd8..a31f75a3d 100644 --- a/compose/service.py +++ b/compose/service.py @@ -656,9 +656,15 @@ class Service(object): return json_hash(self.config_dict()) def config_dict(self): + def image_id(): + try: + return self.image()['Id'] + except NoSuchImageError: + return None + return { 'options': self.options, - 'image_id': self.image()['Id'], + 'image_id': image_id(), 'links': self.get_link_names(), 'net': self.network_mode.id, 'networks': self.networks, diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 815b92c8d..f9d2821b0 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -224,7 +224,6 @@ class CLITestCase(DockerClientTestCase): def test_config_with_hash_option(self): self.base_dir = 'tests/fixtures/v2-full' - self.project.build() result = self.dispatch(['config', '--hash=*']) for service in self.project.get_services(): assert '{} {}\n'.format(service.name, service.config_hash) in result.stdout