Merge pull request #2467 from dnephin/remove_migrate_to_labels

Remove migrate-to-labels.
This commit is contained in:
Aanand Prasad 2015-12-07 14:31:23 +00:00
commit ecf2dd11f9
10 changed files with 7 additions and 484 deletions

View File

@ -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()

View File

@ -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))

View File

@ -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):

View File

@ -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`.

View File

@ -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

View File

@ -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 \

View File

@ -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

View File

@ -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
```

View File

@ -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)

View File

@ -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)