diff --git a/compose/cli/main.py b/compose/cli/main.py index 2eba265b6d..62db5183d5 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -12,7 +12,6 @@ from docker.errors import APIError from requests.exceptions import ReadTimeout from .. import __version__ -from .. import legacy from ..config import ConfigurationError from ..config import parse_environment from ..const import DEFAULT_TIMEOUT @@ -50,7 +49,7 @@ def main(): except KeyboardInterrupt: log.error("\nAborting.") sys.exit(1) - except (UserError, NoSuchService, ConfigurationError, legacy.LegacyError) as e: + except (UserError, NoSuchService, ConfigurationError) as e: log.error(e.msg) sys.exit(1) except NoSuchCommand as e: @@ -142,9 +141,7 @@ class TopLevelCommand(DocoptCommand): stop Stop services unpause Unpause services up Create and start containers - migrate-to-labels Recreate containers to add labels version Show the Docker-Compose version information - """ base_dir = '.' @@ -533,32 +530,6 @@ class TopLevelCommand(DocoptCommand): log_printer = build_log_printer(to_attach, service_names, monochrome) attach_to_logs(project, log_printer, service_names, timeout) - def migrate_to_labels(self, project, _options): - """ - Recreate containers to add labels - - If you're coming from Compose 1.2 or earlier, you'll need to remove or - migrate your existing containers after upgrading Compose. This is - because, as of version 1.3, Compose uses Docker labels to keep track - of containers, and so they need to be recreated with labels added. - - If Compose detects containers that were created without labels, it - will refuse to run so that you don't end up with two sets of them. If - you want to keep using your existing containers (for example, because - they have data volumes you want to preserve) you can migrate them with - the following command: - - docker-compose migrate-to-labels - - Alternatively, if you're not worried about keeping them, you can - remove them - Compose will just create new ones. - - docker rm -f myapp_web_1 myapp_db_1 ... - - Usage: migrate-to-labels - """ - legacy.migrate_project_to_labels(project) - def version(self, project, options): """ Show version informations @@ -601,18 +572,10 @@ def run_one_off_container(container_options, project, service, options): if project.use_networking: project.ensure_network_exists() - try: - container = service.create_container( - quiet=True, - one_off=True, - **container_options) - except APIError: - legacy.check_for_legacy_containers( - project.client, - project.name, - [service.name], - allow_one_off=False) - raise + container = service.create_container( + quiet=True, + one_off=True, + **container_options) if options['-d']: container.start() diff --git a/compose/legacy.py b/compose/legacy.py deleted file mode 100644 index 5416241789..0000000000 --- a/compose/legacy.py +++ /dev/null @@ -1,182 +0,0 @@ -import logging -import re - -from .const import LABEL_VERSION -from .container import Container -from .container import get_container_name - - -log = logging.getLogger(__name__) - - -# TODO: remove this section when migrate_project_to_labels is removed -NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(run_)?(\d+)$') - -ERROR_MESSAGE_FORMAT = """ -Compose found the following containers without labels: - -{names_list} - -As of Compose 1.3.0, containers are identified with labels instead of naming -convention. If you want to continue using these containers, run: - - $ docker-compose migrate-to-labels - -Alternatively, remove them: - - $ docker rm -f {rm_args} -""" - -ONE_OFF_ADDENDUM_FORMAT = """ -You should also remove your one-off containers: - - $ docker rm -f {rm_args} -""" - -ONE_OFF_ERROR_MESSAGE_FORMAT = """ -Compose found the following containers without labels: - -{names_list} - -As of Compose 1.3.0, containers are identified with labels instead of naming convention. - -Remove them before continuing: - - $ docker rm -f {rm_args} -""" - - -def check_for_legacy_containers( - client, - project, - services, - allow_one_off=True): - """Check if there are containers named using the old naming convention - and warn the user that those containers may need to be migrated to - using labels, so that compose can find them. - """ - containers = get_legacy_containers(client, project, services, one_off=False) - - if containers: - one_off_containers = get_legacy_containers(client, project, services, one_off=True) - - raise LegacyContainersError( - [c.name for c in containers], - [c.name for c in one_off_containers], - ) - - if not allow_one_off: - one_off_containers = get_legacy_containers(client, project, services, one_off=True) - - if one_off_containers: - raise LegacyOneOffContainersError( - [c.name for c in one_off_containers], - ) - - -class LegacyError(Exception): - def __unicode__(self): - return self.msg - - __str__ = __unicode__ - - -class LegacyContainersError(LegacyError): - def __init__(self, names, one_off_names): - self.names = names - self.one_off_names = one_off_names - - self.msg = ERROR_MESSAGE_FORMAT.format( - names_list="\n".join(" {}".format(name) for name in names), - rm_args=" ".join(names), - ) - - if one_off_names: - self.msg += ONE_OFF_ADDENDUM_FORMAT.format(rm_args=" ".join(one_off_names)) - - -class LegacyOneOffContainersError(LegacyError): - def __init__(self, one_off_names): - self.one_off_names = one_off_names - - self.msg = ONE_OFF_ERROR_MESSAGE_FORMAT.format( - names_list="\n".join(" {}".format(name) for name in one_off_names), - rm_args=" ".join(one_off_names), - ) - - -def add_labels(project, container): - project_name, service_name, one_off, number = NAME_RE.match(container.name).groups() - if project_name != project.name or service_name not in project.service_names: - return - service = project.get_service(service_name) - service.recreate_container(container) - - -def migrate_project_to_labels(project): - log.info("Running migration to labels for project %s", project.name) - - containers = get_legacy_containers( - project.client, - project.name, - project.service_names, - one_off=False, - ) - - for container in containers: - add_labels(project, container) - - -def get_legacy_containers( - client, - project, - services, - one_off=False): - - return list(_get_legacy_containers_iter( - client, - project, - services, - one_off=one_off, - )) - - -def _get_legacy_containers_iter( - client, - project, - services, - one_off=False): - - containers = client.containers(all=True) - - for service in services: - for container in containers: - if LABEL_VERSION in (container.get('Labels') or {}): - continue - - name = get_container_name(container) - if has_container(project, service, name, one_off=one_off): - yield Container.from_ps(client, container) - - -def has_container(project, service, name, one_off=False): - if not name or not is_valid_name(name, one_off): - return False - container_project, container_service, _container_number = parse_name(name) - return container_project == project and container_service == service - - -def is_valid_name(name, one_off=False): - match = NAME_RE.match(name) - if match is None: - return False - if one_off: - return match.group(3) == 'run_' - else: - return match.group(3) is None - - -def parse_name(name): - match = NAME_RE.match(name) - (project, service_name, _, suffix) = match.groups() - return (project, service_name, int(suffix)) diff --git a/compose/project.py b/compose/project.py index 30e81693c5..af40d820ae 100644 --- a/compose/project.py +++ b/compose/project.py @@ -15,7 +15,6 @@ from .const import LABEL_ONE_OFF from .const import LABEL_PROJECT from .const import LABEL_SERVICE from .container import Container -from .legacy import check_for_legacy_containers from .service import ContainerNet from .service import ConvergenceStrategy from .service import Net @@ -287,13 +286,6 @@ class Project(object): def matches_service_names(container): return container.labels.get(LABEL_SERVICE) in service_names - if not containers: - check_for_legacy_containers( - self.client, - self.name, - self.service_names, - ) - return [c for c in containers if matches_service_names(c)] def get_network(self): diff --git a/compose/service.py b/compose/service.py index 08d563e930..0b9a2aa3bc 100644 --- a/compose/service.py +++ b/compose/service.py @@ -26,7 +26,6 @@ from .const import LABEL_PROJECT from .const import LABEL_SERVICE from .const import LABEL_VERSION from .container import Container -from .legacy import check_for_legacy_containers from .parallel import parallel_execute from .parallel import parallel_remove from .parallel import parallel_start @@ -122,21 +121,12 @@ class Service(object): def containers(self, stopped=False, one_off=False, filters={}): filters.update({'label': self.labels(one_off=one_off)}) - containers = list(filter(None, [ + return list(filter(None, [ Container.from_ps(self.client, container) for container in self.client.containers( all=stopped, filters=filters)])) - if not containers: - check_for_legacy_containers( - self.client, - self.project, - [self.name], - ) - - return containers - def get_container(self, number=1): """Return a :class:`compose.container.Container` for this service. The container must be active, and match `number`. diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index b4f4387f3f..c22e6abc3d 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -164,15 +164,6 @@ _docker_compose_logs() { } -_docker_compose_migrate_to_labels() { - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; - esac -} - - _docker_compose_pause() { case "$cur" in -*) @@ -385,7 +376,6 @@ _docker_compose() { help kill logs - migrate-to-labels pause port ps diff --git a/contrib/completion/zsh/_docker-compose b/contrib/completion/zsh/_docker-compose index 08d5150d9e..0b50b5358b 100644 --- a/contrib/completion/zsh/_docker-compose +++ b/contrib/completion/zsh/_docker-compose @@ -212,11 +212,6 @@ __docker-compose_subcommand() { '--no-color[Produce monochrome output.]' \ '*:services:__docker-compose_services_all' && ret=0 ;; - (migrate-to-labels) - _arguments -A '-*' \ - $opts_help \ - '(-):Recreate containers to add labels' && ret=0 - ;; (pause) _arguments \ $opts_help \ diff --git a/docs/install.md b/docs/install.md index 980d285c56..7c73baaa91 100644 --- a/docs/install.md +++ b/docs/install.md @@ -98,7 +98,7 @@ be recreated with labels added. If Compose detects containers that were created without labels, it will refuse to run so that you don't end up with two sets of them. If you want to keep using your existing containers (for example, because they have data volumes you want -to preserve) you can migrate them with the following command: +to preserve) you can use compose 1.5.x to migrate them with the following command: $ docker-compose migrate-to-labels diff --git a/docs/reference/docker-compose.md b/docs/reference/docker-compose.md index 8712072e56..c19e428483 100644 --- a/docs/reference/docker-compose.md +++ b/docs/reference/docker-compose.md @@ -40,7 +40,6 @@ Commands: stop Stop services unpause Unpause services up Create and start containers - migrate-to-labels Recreate containers to add labels version Show the Docker-Compose version information ``` diff --git a/tests/integration/legacy_test.py b/tests/integration/legacy_test.py deleted file mode 100644 index 3465d57f49..0000000000 --- a/tests/integration/legacy_test.py +++ /dev/null @@ -1,218 +0,0 @@ -import unittest - -from docker.errors import APIError - -from .. import mock -from .testcases import DockerClientTestCase -from compose import legacy -from compose.project import Project - - -class UtilitiesTestCase(unittest.TestCase): - def test_has_container(self): - self.assertTrue( - legacy.has_container("composetest", "web", "composetest_web_1", one_off=False), - ) - self.assertFalse( - legacy.has_container("composetest", "web", "composetest_web_run_1", one_off=False), - ) - - def test_has_container_one_off(self): - self.assertFalse( - legacy.has_container("composetest", "web", "composetest_web_1", one_off=True), - ) - self.assertTrue( - legacy.has_container("composetest", "web", "composetest_web_run_1", one_off=True), - ) - - def test_has_container_different_project(self): - self.assertFalse( - legacy.has_container("composetest", "web", "otherapp_web_1", one_off=False), - ) - self.assertFalse( - legacy.has_container("composetest", "web", "otherapp_web_run_1", one_off=True), - ) - - def test_has_container_different_service(self): - self.assertFalse( - legacy.has_container("composetest", "web", "composetest_db_1", one_off=False), - ) - self.assertFalse( - legacy.has_container("composetest", "web", "composetest_db_run_1", one_off=True), - ) - - def test_is_valid_name(self): - self.assertTrue( - legacy.is_valid_name("composetest_web_1", one_off=False), - ) - self.assertFalse( - legacy.is_valid_name("composetest_web_run_1", one_off=False), - ) - - def test_is_valid_name_one_off(self): - self.assertFalse( - legacy.is_valid_name("composetest_web_1", one_off=True), - ) - self.assertTrue( - legacy.is_valid_name("composetest_web_run_1", one_off=True), - ) - - def test_is_valid_name_invalid(self): - self.assertFalse( - legacy.is_valid_name("foo"), - ) - self.assertFalse( - legacy.is_valid_name("composetest_web_lol_1", one_off=True), - ) - - def test_get_legacy_containers(self): - client = mock.Mock() - client.containers.return_value = [ - { - "Id": "abc123", - "Image": "def456", - "Name": "composetest_web_1", - "Labels": None, - }, - { - "Id": "ghi789", - "Image": "def456", - "Name": None, - "Labels": None, - }, - { - "Id": "jkl012", - "Image": "def456", - "Labels": None, - }, - ] - - containers = legacy.get_legacy_containers(client, "composetest", ["web"]) - - self.assertEqual(len(containers), 1) - self.assertEqual(containers[0].id, 'abc123') - - -class LegacyTestCase(DockerClientTestCase): - - def setUp(self): - super(LegacyTestCase, self).setUp() - self.containers = [] - - db = self.create_service('db') - web = self.create_service('web', links=[(db, 'db')]) - nginx = self.create_service('nginx', links=[(web, 'web')]) - - self.services = [db, web, nginx] - self.project = Project('composetest', self.services, self.client) - - # Create a legacy container for each service - for service in self.services: - service.ensure_image_exists() - container = self.client.create_container( - name='{}_{}_1'.format(self.project.name, service.name), - **service.options - ) - self.client.start(container) - self.containers.append(container) - - # Create a single one-off legacy container - self.containers.append(self.client.create_container( - name='{}_{}_run_1'.format(self.project.name, db.name), - **self.services[0].options - )) - - def tearDown(self): - super(LegacyTestCase, self).tearDown() - for container in self.containers: - try: - self.client.kill(container) - except APIError: - pass - try: - self.client.remove_container(container) - except APIError: - pass - - def get_legacy_containers(self, **kwargs): - return legacy.get_legacy_containers( - self.client, - self.project.name, - [s.name for s in self.services], - **kwargs - ) - - def test_get_legacy_container_names(self): - self.assertEqual(len(self.get_legacy_containers()), len(self.services)) - - def test_get_legacy_container_names_one_off(self): - self.assertEqual(len(self.get_legacy_containers(one_off=True)), 1) - - def test_migration_to_labels(self): - # Trying to get the container list raises an exception - - with self.assertRaises(legacy.LegacyContainersError) as cm: - self.project.containers(stopped=True) - - self.assertEqual( - set(cm.exception.names), - set(['composetest_db_1', 'composetest_web_1', 'composetest_nginx_1']), - ) - - self.assertEqual( - set(cm.exception.one_off_names), - set(['composetest_db_run_1']), - ) - - # Migrate the containers - - legacy.migrate_project_to_labels(self.project) - - # Getting the list no longer raises an exception - - containers = self.project.containers(stopped=True) - self.assertEqual(len(containers), len(self.services)) - - def test_migration_one_off(self): - # We've already migrated - - legacy.migrate_project_to_labels(self.project) - - # Trying to create a one-off container results in a Docker API error - - with self.assertRaises(APIError) as cm: - self.project.get_service('db').create_container(one_off=True) - - # Checking for legacy one-off containers raises an exception - - with self.assertRaises(legacy.LegacyOneOffContainersError) as cm: - legacy.check_for_legacy_containers( - self.client, - self.project.name, - ['db'], - allow_one_off=False, - ) - - self.assertEqual( - set(cm.exception.one_off_names), - set(['composetest_db_run_1']), - ) - - # Remove the old one-off container - - c = self.client.inspect_container('composetest_db_run_1') - self.client.remove_container(c) - - # Checking no longer raises an exception - - legacy.check_for_legacy_containers( - self.client, - self.project.name, - ['db'], - allow_one_off=False, - ) - - # Creating a one-off container no longer results in an API error - - self.project.get_service('db').create_container(one_off=True) - self.assertIsInstance(self.client.inspect_container('composetest_db_run_1'), dict) diff --git a/tests/unit/cli_test.py b/tests/unit/cli_test.py index c962d0070c..61cef6f6a3 100644 --- a/tests/unit/cli_test.py +++ b/tests/unit/cli_test.py @@ -74,12 +74,6 @@ class CLITestCase(unittest.TestCase): self.assertIn('Usage: up', str(ctx.exception)) - def test_command_help_dashes(self): - with self.assertRaises(SystemExit) as ctx: - TopLevelCommand().dispatch(['help', 'migrate-to-labels'], None) - - self.assertIn('Usage: migrate-to-labels', str(ctx.exception)) - def test_command_help_nonexistent(self): with self.assertRaises(NoSuchCommand): TopLevelCommand().dispatch(['help', 'nonexistent'], None)