mirror of https://github.com/docker/compose.git
Merge pull request #5896 from docker/5874-legacy-proj-name
Retrieve objects using legacy (< 1.21) project names
This commit is contained in:
commit
faa532c315
|
@ -2,6 +2,7 @@ from __future__ import absolute_import
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
|
||||
from docker.errors import NotFound
|
||||
|
@ -10,9 +11,11 @@ from docker.types import IPAMPool
|
|||
from docker.utils import version_gte
|
||||
from docker.utils import version_lt
|
||||
|
||||
from . import __version__
|
||||
from .config import ConfigurationError
|
||||
from .const import LABEL_NETWORK
|
||||
from .const import LABEL_PROJECT
|
||||
from .const import LABEL_VERSION
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -39,6 +42,7 @@ class Network(object):
|
|||
self.enable_ipv6 = enable_ipv6
|
||||
self.labels = labels
|
||||
self.custom_name = custom_name
|
||||
self.legacy = None
|
||||
|
||||
def ensure(self):
|
||||
if self.external:
|
||||
|
@ -64,8 +68,9 @@ class Network(object):
|
|||
)
|
||||
return
|
||||
|
||||
self._set_legacy_flag()
|
||||
try:
|
||||
data = self.inspect()
|
||||
data = self.inspect(legacy=self.legacy)
|
||||
check_remote_network_config(data, self)
|
||||
except NotFound:
|
||||
driver_name = 'the default driver'
|
||||
|
@ -73,8 +78,7 @@ class Network(object):
|
|||
driver_name = 'driver "{}"'.format(self.driver)
|
||||
|
||||
log.info(
|
||||
'Creating network "{}" with {}'
|
||||
.format(self.full_name, driver_name)
|
||||
'Creating network "{}" with {}'.format(self.full_name, driver_name)
|
||||
)
|
||||
|
||||
self.client.create_network(
|
||||
|
@ -91,21 +95,38 @@ class Network(object):
|
|||
|
||||
def remove(self):
|
||||
if self.external:
|
||||
log.info("Network %s is external, skipping", self.full_name)
|
||||
log.info("Network %s is external, skipping", self.true_name)
|
||||
return
|
||||
|
||||
log.info("Removing network {}".format(self.full_name))
|
||||
self.client.remove_network(self.full_name)
|
||||
log.info("Removing network {}".format(self.true_name))
|
||||
self.client.remove_network(self.true_name)
|
||||
|
||||
def inspect(self):
|
||||
def inspect(self, legacy=False):
|
||||
if legacy:
|
||||
return self.client.inspect_network(self.legacy_full_name)
|
||||
return self.client.inspect_network(self.full_name)
|
||||
|
||||
@property
|
||||
def legacy_full_name(self):
|
||||
if self.custom_name:
|
||||
return self.name
|
||||
return '{0}_{1}'.format(
|
||||
re.sub(r'[_-]', '', self.project), self.name
|
||||
)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
if self.custom_name:
|
||||
return self.name
|
||||
return '{0}_{1}'.format(self.project, self.name)
|
||||
|
||||
@property
|
||||
def true_name(self):
|
||||
self._set_legacy_flag()
|
||||
if self.legacy:
|
||||
return self.legacy_full_name
|
||||
return self.full_name
|
||||
|
||||
@property
|
||||
def _labels(self):
|
||||
if version_lt(self.client._version, '1.23'):
|
||||
|
@ -114,9 +135,19 @@ class Network(object):
|
|||
labels.update({
|
||||
LABEL_PROJECT: self.project,
|
||||
LABEL_NETWORK: self.name,
|
||||
LABEL_VERSION: __version__,
|
||||
})
|
||||
return labels
|
||||
|
||||
def _set_legacy_flag(self):
|
||||
if self.legacy is not None:
|
||||
return
|
||||
try:
|
||||
data = self.inspect(legacy=True)
|
||||
self.legacy = data is not None
|
||||
except NotFound:
|
||||
self.legacy = False
|
||||
|
||||
|
||||
def create_ipam_config_from_dict(ipam_dict):
|
||||
if not ipam_dict:
|
||||
|
@ -150,49 +181,49 @@ def check_remote_ipam_config(remote, local):
|
|||
remote_ipam = remote.get('IPAM')
|
||||
ipam_dict = create_ipam_config_from_dict(local.ipam)
|
||||
if local.ipam.get('driver') and local.ipam.get('driver') != remote_ipam.get('Driver'):
|
||||
raise NetworkConfigChangedError(local.full_name, 'IPAM driver')
|
||||
raise NetworkConfigChangedError(local.true_name, 'IPAM driver')
|
||||
if len(ipam_dict['Config']) != 0:
|
||||
if len(ipam_dict['Config']) != len(remote_ipam['Config']):
|
||||
raise NetworkConfigChangedError(local.full_name, 'IPAM configs')
|
||||
raise NetworkConfigChangedError(local.true_name, 'IPAM configs')
|
||||
remote_configs = sorted(remote_ipam['Config'], key='Subnet')
|
||||
local_configs = sorted(ipam_dict['Config'], key='Subnet')
|
||||
while local_configs:
|
||||
lc = local_configs.pop()
|
||||
rc = remote_configs.pop()
|
||||
if lc.get('Subnet') != rc.get('Subnet'):
|
||||
raise NetworkConfigChangedError(local.full_name, 'IPAM config subnet')
|
||||
raise NetworkConfigChangedError(local.true_name, 'IPAM config subnet')
|
||||
if lc.get('Gateway') is not None and lc.get('Gateway') != rc.get('Gateway'):
|
||||
raise NetworkConfigChangedError(local.full_name, 'IPAM config gateway')
|
||||
raise NetworkConfigChangedError(local.true_name, 'IPAM config gateway')
|
||||
if lc.get('IPRange') != rc.get('IPRange'):
|
||||
raise NetworkConfigChangedError(local.full_name, 'IPAM config ip_range')
|
||||
raise NetworkConfigChangedError(local.true_name, 'IPAM config ip_range')
|
||||
if sorted(lc.get('AuxiliaryAddresses')) != sorted(rc.get('AuxiliaryAddresses')):
|
||||
raise NetworkConfigChangedError(local.full_name, 'IPAM config aux_addresses')
|
||||
raise NetworkConfigChangedError(local.true_name, 'IPAM config aux_addresses')
|
||||
|
||||
remote_opts = remote_ipam.get('Options') or {}
|
||||
local_opts = local.ipam.get('Options') or {}
|
||||
for k in set.union(set(remote_opts.keys()), set(local_opts.keys())):
|
||||
if remote_opts.get(k) != local_opts.get(k):
|
||||
raise NetworkConfigChangedError(local.full_name, 'IPAM option "{}"'.format(k))
|
||||
raise NetworkConfigChangedError(local.true_name, 'IPAM option "{}"'.format(k))
|
||||
|
||||
|
||||
def check_remote_network_config(remote, local):
|
||||
if local.driver and remote.get('Driver') != local.driver:
|
||||
raise NetworkConfigChangedError(local.full_name, 'driver')
|
||||
raise NetworkConfigChangedError(local.true_name, 'driver')
|
||||
local_opts = local.driver_opts or {}
|
||||
remote_opts = remote.get('Options') or {}
|
||||
for k in set.union(set(remote_opts.keys()), set(local_opts.keys())):
|
||||
if k in OPTS_EXCEPTIONS:
|
||||
continue
|
||||
if remote_opts.get(k) != local_opts.get(k):
|
||||
raise NetworkConfigChangedError(local.full_name, 'option "{}"'.format(k))
|
||||
raise NetworkConfigChangedError(local.true_name, 'option "{}"'.format(k))
|
||||
|
||||
if local.ipam is not None:
|
||||
check_remote_ipam_config(remote, local)
|
||||
|
||||
if local.internal is not None and local.internal != remote.get('Internal', False):
|
||||
raise NetworkConfigChangedError(local.full_name, 'internal')
|
||||
raise NetworkConfigChangedError(local.true_name, 'internal')
|
||||
if local.enable_ipv6 is not None and local.enable_ipv6 != remote.get('EnableIPv6', False):
|
||||
raise NetworkConfigChangedError(local.full_name, 'enable_ipv6')
|
||||
raise NetworkConfigChangedError(local.true_name, 'enable_ipv6')
|
||||
|
||||
local_labels = local.labels or {}
|
||||
remote_labels = remote.get('Labels', {})
|
||||
|
@ -202,7 +233,7 @@ def check_remote_network_config(remote, local):
|
|||
if remote_labels.get(k) != local_labels.get(k):
|
||||
log.warn(
|
||||
'Network {}: label "{}" has changed. It may need to be'
|
||||
' recreated.'.format(local.full_name, k)
|
||||
' recreated.'.format(local.true_name, k)
|
||||
)
|
||||
|
||||
|
||||
|
@ -257,7 +288,7 @@ class ProjectNetworks(object):
|
|||
try:
|
||||
network.remove()
|
||||
except NotFound:
|
||||
log.warn("Network %s not found.", network.full_name)
|
||||
log.warn("Network %s not found.", network.true_name)
|
||||
|
||||
def initialize(self):
|
||||
if not self.use_networking:
|
||||
|
@ -286,7 +317,7 @@ def get_networks(service_dict, network_definitions):
|
|||
for name, netdef in get_network_defs_for_service(service_dict).items():
|
||||
network = network_definitions.get(name)
|
||||
if network:
|
||||
networks[network.full_name] = netdef
|
||||
networks[network.true_name] = netdef
|
||||
else:
|
||||
raise ConfigurationError(
|
||||
'Service "{}" uses an undefined network "{}"'
|
||||
|
|
|
@ -51,6 +51,7 @@ from .progress_stream import StreamOutputError
|
|||
from .utils import json_hash
|
||||
from .utils import parse_bytes
|
||||
from .utils import parse_seconds_float
|
||||
from .version import ComposeVersion
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -192,11 +193,25 @@ class Service(object):
|
|||
def containers(self, stopped=False, one_off=False, filters={}):
|
||||
filters.update({'label': self.labels(one_off=one_off)})
|
||||
|
||||
return list(filter(None, [
|
||||
result = list(filter(None, [
|
||||
Container.from_ps(self.client, container)
|
||||
for container in self.client.containers(
|
||||
all=stopped,
|
||||
filters=filters)]))
|
||||
filters=filters)])
|
||||
)
|
||||
if result:
|
||||
return result
|
||||
|
||||
filters.update({'label': self.labels(one_off=one_off, legacy=True)})
|
||||
return list(
|
||||
filter(
|
||||
self.has_legacy_proj_name, filter(None, [
|
||||
Container.from_ps(self.client, container)
|
||||
for container in self.client.containers(
|
||||
all=stopped,
|
||||
filters=filters)])
|
||||
)
|
||||
)
|
||||
|
||||
def get_container(self, number=1):
|
||||
"""Return a :class:`compose.container.Container` for this service. The
|
||||
|
@ -380,6 +395,10 @@ class Service(object):
|
|||
has_diverged = False
|
||||
|
||||
for c in containers:
|
||||
if self.has_legacy_proj_name(c):
|
||||
log.debug('%s has diverged: Legacy project name' % c.name)
|
||||
has_diverged = True
|
||||
continue
|
||||
container_config_hash = c.labels.get(LABEL_CONFIG_HASH, None)
|
||||
if container_config_hash != config_hash:
|
||||
log.debug(
|
||||
|
@ -1053,11 +1072,12 @@ class Service(object):
|
|||
def can_be_built(self):
|
||||
return 'build' in self.options
|
||||
|
||||
def labels(self, one_off=False):
|
||||
def labels(self, one_off=False, legacy=False):
|
||||
proj_name = self.project if not legacy else re.sub(r'[_-]', '', self.project)
|
||||
return [
|
||||
'{0}={1}'.format(LABEL_PROJECT, self.project),
|
||||
'{0}={1}'.format(LABEL_PROJECT, proj_name),
|
||||
'{0}={1}'.format(LABEL_SERVICE, self.name),
|
||||
'{0}={1}'.format(LABEL_ONE_OFF, "True" if one_off else "False")
|
||||
'{0}={1}'.format(LABEL_ONE_OFF, "True" if one_off else "False"),
|
||||
]
|
||||
|
||||
@property
|
||||
|
@ -1214,6 +1234,12 @@ class Service(object):
|
|||
|
||||
return result
|
||||
|
||||
def has_legacy_proj_name(self, ctnr):
|
||||
return (
|
||||
ComposeVersion(ctnr.labels.get(LABEL_VERSION)) < ComposeVersion('1.21.0') and
|
||||
ctnr.project != self.project
|
||||
)
|
||||
|
||||
|
||||
def short_id_alias_exists(container, network):
|
||||
aliases = container.get(
|
||||
|
|
|
@ -2,15 +2,19 @@ from __future__ import absolute_import
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from docker.errors import NotFound
|
||||
from docker.utils import version_lt
|
||||
|
||||
from . import __version__
|
||||
from .config import ConfigurationError
|
||||
from .config.types import VolumeSpec
|
||||
from .const import LABEL_PROJECT
|
||||
from .const import LABEL_VERSION
|
||||
from .const import LABEL_VOLUME
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -25,6 +29,7 @@ class Volume(object):
|
|||
self.external = external
|
||||
self.labels = labels
|
||||
self.custom_name = custom_name
|
||||
self.legacy = None
|
||||
|
||||
def create(self):
|
||||
return self.client.create_volume(
|
||||
|
@ -33,17 +38,20 @@ class Volume(object):
|
|||
|
||||
def remove(self):
|
||||
if self.external:
|
||||
log.info("Volume %s is external, skipping", self.full_name)
|
||||
log.info("Volume %s is external, skipping", self.true_name)
|
||||
return
|
||||
log.info("Removing volume %s", self.full_name)
|
||||
return self.client.remove_volume(self.full_name)
|
||||
log.info("Removing volume %s", self.true_name)
|
||||
return self.client.remove_volume(self.true_name)
|
||||
|
||||
def inspect(self):
|
||||
def inspect(self, legacy=None):
|
||||
if legacy:
|
||||
return self.client.inspect_volume(self.legacy_full_name)
|
||||
return self.client.inspect_volume(self.full_name)
|
||||
|
||||
def exists(self):
|
||||
self._set_legacy_flag()
|
||||
try:
|
||||
self.inspect()
|
||||
self.inspect(legacy=self.legacy)
|
||||
except NotFound:
|
||||
return False
|
||||
return True
|
||||
|
@ -54,6 +62,21 @@ class Volume(object):
|
|||
return self.name
|
||||
return '{0}_{1}'.format(self.project, self.name)
|
||||
|
||||
@property
|
||||
def legacy_full_name(self):
|
||||
if self.custom_name:
|
||||
return self.name
|
||||
return '{0}_{1}'.format(
|
||||
re.sub(r'[_-]', '', self.project), self.name
|
||||
)
|
||||
|
||||
@property
|
||||
def true_name(self):
|
||||
self._set_legacy_flag()
|
||||
if self.legacy:
|
||||
return self.legacy_full_name
|
||||
return self.full_name
|
||||
|
||||
@property
|
||||
def _labels(self):
|
||||
if version_lt(self.client._version, '1.23'):
|
||||
|
@ -62,9 +85,19 @@ class Volume(object):
|
|||
labels.update({
|
||||
LABEL_PROJECT: self.project,
|
||||
LABEL_VOLUME: self.name,
|
||||
LABEL_VERSION: __version__,
|
||||
})
|
||||
return labels
|
||||
|
||||
def _set_legacy_flag(self):
|
||||
if self.legacy is not None:
|
||||
return
|
||||
try:
|
||||
data = self.inspect(legacy=True)
|
||||
self.legacy = data is not None
|
||||
except NotFound:
|
||||
self.legacy = False
|
||||
|
||||
|
||||
class ProjectVolumes(object):
|
||||
|
||||
|
@ -94,7 +127,7 @@ class ProjectVolumes(object):
|
|||
try:
|
||||
volume.remove()
|
||||
except NotFound:
|
||||
log.warn("Volume %s not found.", volume.full_name)
|
||||
log.warn("Volume %s not found.", volume.true_name)
|
||||
|
||||
def initialize(self):
|
||||
try:
|
||||
|
@ -124,7 +157,7 @@ class ProjectVolumes(object):
|
|||
)
|
||||
volume.create()
|
||||
else:
|
||||
check_remote_volume_config(volume.inspect(), volume)
|
||||
check_remote_volume_config(volume.inspect(legacy=volume.legacy), volume)
|
||||
except NotFound:
|
||||
raise ConfigurationError(
|
||||
'Volume %s specifies nonexistent driver %s' % (volume.name, volume.driver)
|
||||
|
@ -136,9 +169,9 @@ class ProjectVolumes(object):
|
|||
|
||||
if isinstance(volume_spec, VolumeSpec):
|
||||
volume = self.volumes[volume_spec.external]
|
||||
return volume_spec._replace(external=volume.full_name)
|
||||
return volume_spec._replace(external=volume.true_name)
|
||||
else:
|
||||
volume_spec.source = self.volumes[volume_spec.source].full_name
|
||||
volume_spec.source = self.volumes[volume_spec.source].true_name
|
||||
return volume_spec
|
||||
|
||||
|
||||
|
@ -152,7 +185,7 @@ class VolumeConfigChangedError(ConfigurationError):
|
|||
'first:\n$ docker volume rm {full_name}'.format(
|
||||
vol_name=local.name, property_name=property_name,
|
||||
local_value=local_value, remote_value=remote_value,
|
||||
full_name=local.full_name
|
||||
full_name=local.true_name
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ class NetworkTest(unittest.TestCase):
|
|||
{'Driver': 'overlay', 'Options': remote_options}, net
|
||||
)
|
||||
|
||||
@mock.patch('compose.network.Network.true_name', lambda n: n.full_name)
|
||||
def test_check_remote_network_config_driver_mismatch(self):
|
||||
net = Network(None, 'compose_test', 'net1', 'overlay')
|
||||
with pytest.raises(NetworkConfigChangedError) as e:
|
||||
|
@ -93,6 +94,7 @@ class NetworkTest(unittest.TestCase):
|
|||
|
||||
assert 'driver has changed' in str(e.value)
|
||||
|
||||
@mock.patch('compose.network.Network.true_name', lambda n: n.full_name)
|
||||
def test_check_remote_network_config_options_mismatch(self):
|
||||
net = Network(None, 'compose_test', 'net1', 'overlay')
|
||||
with pytest.raises(NetworkConfigChangedError) as e:
|
||||
|
@ -146,6 +148,7 @@ class NetworkTest(unittest.TestCase):
|
|||
net
|
||||
)
|
||||
|
||||
@mock.patch('compose.network.Network.true_name', lambda n: n.full_name)
|
||||
def test_check_remote_network_labels_mismatch(self):
|
||||
net = Network(None, 'compose_test', 'net1', 'overlay', labels={
|
||||
'com.project.touhou.character': 'sakuya.izayoi'
|
||||
|
|
|
@ -60,6 +60,7 @@ class ProjectTest(unittest.TestCase):
|
|||
assert project.get_service('db').options['image'] == 'busybox:latest'
|
||||
assert not project.networks.use_networking
|
||||
|
||||
@mock.patch('compose.network.Network.true_name', lambda n: n.full_name)
|
||||
def test_from_config_v2(self):
|
||||
config = Config(
|
||||
version=V2_0,
|
||||
|
@ -217,6 +218,7 @@ class ProjectTest(unittest.TestCase):
|
|||
)
|
||||
assert project.get_service('test')._get_volumes_from() == [container_name + ":rw"]
|
||||
|
||||
@mock.patch('compose.network.Network.true_name', lambda n: n.full_name)
|
||||
def test_use_volumes_from_service_container(self):
|
||||
container_ids = ['aabbccddee', '12345']
|
||||
|
||||
|
|
Loading…
Reference in New Issue