mirror of
https://github.com/docker/compose.git
synced 2025-07-28 08:04:09 +02:00
Merge pull request #2907 from shin-/2829-net-alias
Allow user to specify custom network aliases
This commit is contained in:
commit
5fc0df4be2
@ -612,6 +612,9 @@ def finalize_service(service_config, service_names, version):
|
|||||||
else:
|
else:
|
||||||
service_dict['network_mode'] = network_mode
|
service_dict['network_mode'] = network_mode
|
||||||
|
|
||||||
|
if 'networks' in service_dict:
|
||||||
|
service_dict['networks'] = parse_networks(service_dict['networks'])
|
||||||
|
|
||||||
if 'restart' in service_dict:
|
if 'restart' in service_dict:
|
||||||
service_dict['restart'] = parse_restart_spec(service_dict['restart'])
|
service_dict['restart'] = parse_restart_spec(service_dict['restart'])
|
||||||
|
|
||||||
@ -701,6 +704,7 @@ def merge_service_dicts(base, override, version):
|
|||||||
md.merge_mapping('environment', parse_environment)
|
md.merge_mapping('environment', parse_environment)
|
||||||
md.merge_mapping('labels', parse_labels)
|
md.merge_mapping('labels', parse_labels)
|
||||||
md.merge_mapping('ulimits', parse_ulimits)
|
md.merge_mapping('ulimits', parse_ulimits)
|
||||||
|
md.merge_mapping('networks', parse_networks)
|
||||||
md.merge_sequence('links', ServiceLink.parse)
|
md.merge_sequence('links', ServiceLink.parse)
|
||||||
|
|
||||||
for field in ['volumes', 'devices']:
|
for field in ['volumes', 'devices']:
|
||||||
@ -710,7 +714,6 @@ def merge_service_dicts(base, override, version):
|
|||||||
'depends_on',
|
'depends_on',
|
||||||
'expose',
|
'expose',
|
||||||
'external_links',
|
'external_links',
|
||||||
'networks',
|
|
||||||
'ports',
|
'ports',
|
||||||
'volumes_from',
|
'volumes_from',
|
||||||
]:
|
]:
|
||||||
@ -798,6 +801,7 @@ def parse_dict_or_list(split_func, type_name, arguments):
|
|||||||
parse_build_arguments = functools.partial(parse_dict_or_list, split_env, 'build arguments')
|
parse_build_arguments = functools.partial(parse_dict_or_list, split_env, 'build arguments')
|
||||||
parse_environment = functools.partial(parse_dict_or_list, split_env, 'environment')
|
parse_environment = functools.partial(parse_dict_or_list, split_env, 'environment')
|
||||||
parse_labels = functools.partial(parse_dict_or_list, split_label, 'labels')
|
parse_labels = functools.partial(parse_dict_or_list, split_label, 'labels')
|
||||||
|
parse_networks = functools.partial(parse_dict_or_list, lambda k: (k, None), 'networks')
|
||||||
|
|
||||||
|
|
||||||
def parse_ulimits(ulimits):
|
def parse_ulimits(ulimits):
|
||||||
|
@ -120,11 +120,28 @@
|
|||||||
"network_mode": {"type": "string"},
|
"network_mode": {"type": "string"},
|
||||||
|
|
||||||
"networks": {
|
"networks": {
|
||||||
"type": "array",
|
"oneOf": [
|
||||||
"items": {"type": "string"},
|
{"$ref": "#/definitions/list_of_strings"},
|
||||||
"uniqueItems": true
|
{
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^[a-zA-Z0-9._-]+$": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"aliases": {"$ref": "#/definitions/list_of_strings"}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{"type": "null"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"pid": {"type": ["string", "null"]},
|
"pid": {"type": ["string", "null"]},
|
||||||
|
|
||||||
"ports": {
|
"ports": {
|
||||||
|
@ -159,18 +159,26 @@ class ProjectNetworks(object):
|
|||||||
network.ensure()
|
network.ensure()
|
||||||
|
|
||||||
|
|
||||||
def get_network_names_for_service(service_dict):
|
def get_network_aliases_for_service(service_dict):
|
||||||
if 'network_mode' in service_dict:
|
if 'network_mode' in service_dict:
|
||||||
return []
|
return {}
|
||||||
return service_dict.get('networks', ['default'])
|
networks = service_dict.get('networks', {'default': None})
|
||||||
|
return dict(
|
||||||
|
(net, (config or {}).get('aliases', []))
|
||||||
|
for net, config in networks.items()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_network_names_for_service(service_dict):
|
||||||
|
return get_network_aliases_for_service(service_dict).keys()
|
||||||
|
|
||||||
|
|
||||||
def get_networks(service_dict, network_definitions):
|
def get_networks(service_dict, network_definitions):
|
||||||
networks = []
|
networks = {}
|
||||||
for name in get_network_names_for_service(service_dict):
|
for name, aliases in get_network_aliases_for_service(service_dict).items():
|
||||||
network = network_definitions.get(name)
|
network = network_definitions.get(name)
|
||||||
if network:
|
if network:
|
||||||
networks.append(network.full_name)
|
networks[network.full_name] = aliases
|
||||||
else:
|
else:
|
||||||
raise ConfigurationError(
|
raise ConfigurationError(
|
||||||
'Service "{}" uses an undefined network "{}"'
|
'Service "{}" uses an undefined network "{}"'
|
||||||
|
@ -69,11 +69,13 @@ class Project(object):
|
|||||||
if use_networking:
|
if use_networking:
|
||||||
service_networks = get_networks(service_dict, networks)
|
service_networks = get_networks(service_dict, networks)
|
||||||
else:
|
else:
|
||||||
service_networks = []
|
service_networks = {}
|
||||||
|
|
||||||
service_dict.pop('networks', None)
|
service_dict.pop('networks', None)
|
||||||
links = project.get_links(service_dict)
|
links = project.get_links(service_dict)
|
||||||
network_mode = project.get_network_mode(service_dict, service_networks)
|
network_mode = project.get_network_mode(
|
||||||
|
service_dict, list(service_networks.keys())
|
||||||
|
)
|
||||||
volumes_from = get_volumes_from(project, service_dict)
|
volumes_from = get_volumes_from(project, service_dict)
|
||||||
|
|
||||||
if config_data.version != V1:
|
if config_data.version != V1:
|
||||||
|
@ -124,7 +124,7 @@ class Service(object):
|
|||||||
self.links = links or []
|
self.links = links or []
|
||||||
self.volumes_from = volumes_from or []
|
self.volumes_from = volumes_from or []
|
||||||
self.network_mode = network_mode or NetworkMode(None)
|
self.network_mode = network_mode or NetworkMode(None)
|
||||||
self.networks = networks or []
|
self.networks = networks or {}
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|
||||||
def containers(self, stopped=False, one_off=False, filters={}):
|
def containers(self, stopped=False, one_off=False, filters={}):
|
||||||
@ -432,14 +432,14 @@ class Service(object):
|
|||||||
def connect_container_to_networks(self, container):
|
def connect_container_to_networks(self, container):
|
||||||
connected_networks = container.get('NetworkSettings.Networks')
|
connected_networks = container.get('NetworkSettings.Networks')
|
||||||
|
|
||||||
for network in self.networks:
|
for network, aliases in self.networks.items():
|
||||||
if network in connected_networks:
|
if network in connected_networks:
|
||||||
self.client.disconnect_container_from_network(
|
self.client.disconnect_container_from_network(
|
||||||
container.id, network)
|
container.id, network)
|
||||||
|
|
||||||
self.client.connect_container_to_network(
|
self.client.connect_container_to_network(
|
||||||
container.id, network,
|
container.id, network,
|
||||||
aliases=self._get_aliases(container),
|
aliases=list(self._get_aliases(container).union(aliases)),
|
||||||
links=self._get_links(False),
|
links=self._get_links(False),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -473,7 +473,7 @@ class Service(object):
|
|||||||
'image_id': self.image()['Id'],
|
'image_id': self.image()['Id'],
|
||||||
'links': self.get_link_names(),
|
'links': self.get_link_names(),
|
||||||
'net': self.network_mode.id,
|
'net': self.network_mode.id,
|
||||||
'networks': self.networks,
|
'networks': list(self.networks.keys()),
|
||||||
'volumes_from': [
|
'volumes_from': [
|
||||||
(v.source.name, v.mode)
|
(v.source.name, v.mode)
|
||||||
for v in self.volumes_from if isinstance(v.source, Service)
|
for v in self.volumes_from if isinstance(v.source, Service)
|
||||||
@ -514,9 +514,9 @@ class Service(object):
|
|||||||
|
|
||||||
def _get_aliases(self, container):
|
def _get_aliases(self, container):
|
||||||
if container.labels.get(LABEL_ONE_OFF) == "True":
|
if container.labels.get(LABEL_ONE_OFF) == "True":
|
||||||
return []
|
return set()
|
||||||
|
|
||||||
return [self.name, container.short_id]
|
return {self.name, container.short_id}
|
||||||
|
|
||||||
def _get_links(self, link_to_self):
|
def _get_links(self, link_to_self):
|
||||||
links = {}
|
links = {}
|
||||||
|
@ -453,7 +453,7 @@ id.
|
|||||||
|
|
||||||
### network_mode
|
### network_mode
|
||||||
|
|
||||||
> [Version 2 file format](#version-1) only. In version 1, use [net](#net).
|
> [Version 2 file format](#version-2) only. In version 1, use [net](#net).
|
||||||
|
|
||||||
Network mode. Use the same values as the docker client `--net` parameter, plus
|
Network mode. Use the same values as the docker client `--net` parameter, plus
|
||||||
the special form `service:[service name]`.
|
the special form `service:[service name]`.
|
||||||
@ -475,6 +475,27 @@ Networks to join, referencing entries under the
|
|||||||
- some-network
|
- some-network
|
||||||
- other-network
|
- other-network
|
||||||
|
|
||||||
|
#### aliases
|
||||||
|
|
||||||
|
Aliases (alternative hostnames) for this service on the network. Other servers
|
||||||
|
on the network can use either the service name or this alias to connect to
|
||||||
|
this service. Since `alias` is network-scoped:
|
||||||
|
|
||||||
|
* the same service can have different aliases when connected to another
|
||||||
|
network.
|
||||||
|
* it is allowable to configure the same alias name to multiple containers
|
||||||
|
(services) on the same network.
|
||||||
|
|
||||||
|
|
||||||
|
networks:
|
||||||
|
some-network:
|
||||||
|
aliases:
|
||||||
|
- alias1
|
||||||
|
- alias3
|
||||||
|
other-network:
|
||||||
|
aliases:
|
||||||
|
- alias2
|
||||||
|
|
||||||
### pid
|
### pid
|
||||||
|
|
||||||
pid: "host"
|
pid: "host"
|
||||||
|
@ -185,7 +185,7 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
'build': {
|
'build': {
|
||||||
'context': os.path.abspath(self.base_dir),
|
'context': os.path.abspath(self.base_dir),
|
||||||
},
|
},
|
||||||
'networks': ['front', 'default'],
|
'networks': {'front': None, 'default': None},
|
||||||
'volumes_from': ['service:other:rw'],
|
'volumes_from': ['service:other:rw'],
|
||||||
},
|
},
|
||||||
'other': {
|
'other': {
|
||||||
@ -445,6 +445,34 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
|
|
||||||
assert networks[0]['Options']['com.docker.network.bridge.enable_icc'] == 'false'
|
assert networks[0]['Options']['com.docker.network.bridge.enable_icc'] == 'false'
|
||||||
|
|
||||||
|
@v2_only()
|
||||||
|
def test_up_with_network_aliases(self):
|
||||||
|
filename = 'network-aliases.yml'
|
||||||
|
self.base_dir = 'tests/fixtures/networks'
|
||||||
|
self.dispatch(['-f', filename, 'up', '-d'], None)
|
||||||
|
back_name = '{}_back'.format(self.project.name)
|
||||||
|
front_name = '{}_front'.format(self.project.name)
|
||||||
|
|
||||||
|
networks = [
|
||||||
|
n for n in self.client.networks()
|
||||||
|
if n['Name'].startswith('{}_'.format(self.project.name))
|
||||||
|
]
|
||||||
|
|
||||||
|
# Two networks were created: back and front
|
||||||
|
assert sorted(n['Name'] for n in networks) == [back_name, front_name]
|
||||||
|
web_container = self.project.get_service('web').containers()[0]
|
||||||
|
|
||||||
|
back_aliases = web_container.get(
|
||||||
|
'NetworkSettings.Networks.{}.Aliases'.format(back_name)
|
||||||
|
)
|
||||||
|
assert 'web' in back_aliases
|
||||||
|
front_aliases = web_container.get(
|
||||||
|
'NetworkSettings.Networks.{}.Aliases'.format(front_name)
|
||||||
|
)
|
||||||
|
assert 'web' in front_aliases
|
||||||
|
assert 'forward_facing' in front_aliases
|
||||||
|
assert 'ahead' in front_aliases
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_up_with_networks(self):
|
def test_up_with_networks(self):
|
||||||
self.base_dir = 'tests/fixtures/networks'
|
self.base_dir = 'tests/fixtures/networks'
|
||||||
|
16
tests/fixtures/networks/network-aliases.yml
vendored
Normal file
16
tests/fixtures/networks/network-aliases.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks:
|
||||||
|
front:
|
||||||
|
aliases:
|
||||||
|
- forward_facing
|
||||||
|
- ahead
|
||||||
|
back:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
front: {}
|
||||||
|
back: {}
|
@ -565,7 +565,7 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': 'top',
|
'command': 'top',
|
||||||
'networks': ['foo', 'bar', 'baz'],
|
'networks': {'foo': None, 'bar': None, 'baz': None},
|
||||||
}],
|
}],
|
||||||
volumes={},
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
@ -598,7 +598,7 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'networks': ['front'],
|
'networks': {'front': None},
|
||||||
}],
|
}],
|
||||||
volumes={},
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
|
@ -649,6 +649,42 @@ class ConfigTest(unittest.TestCase):
|
|||||||
assert service['build']['args']['opt1'] == '42'
|
assert service['build']['args']['opt1'] == '42'
|
||||||
assert service['build']['args']['opt2'] == 'foobar'
|
assert service['build']['args']['opt2'] == 'foobar'
|
||||||
|
|
||||||
|
def test_load_with_multiple_files_mismatched_networks_format(self):
|
||||||
|
base_file = config.ConfigFile(
|
||||||
|
'base.yaml',
|
||||||
|
{
|
||||||
|
'version': '2',
|
||||||
|
'services': {
|
||||||
|
'web': {
|
||||||
|
'image': 'example/web',
|
||||||
|
'networks': {
|
||||||
|
'foobar': {'aliases': ['foo', 'bar']}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'networks': {'foobar': {}, 'baz': {}}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
override_file = config.ConfigFile(
|
||||||
|
'override.yaml',
|
||||||
|
{
|
||||||
|
'version': '2',
|
||||||
|
'services': {
|
||||||
|
'web': {
|
||||||
|
'networks': ['baz']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
details = config.ConfigDetails('.', [base_file, override_file])
|
||||||
|
web_service = config.load(details).services[0]
|
||||||
|
assert web_service['networks'] == {
|
||||||
|
'foobar': {'aliases': ['foo', 'bar']},
|
||||||
|
'baz': None
|
||||||
|
}
|
||||||
|
|
||||||
def test_load_with_multiple_files_v2(self):
|
def test_load_with_multiple_files_v2(self):
|
||||||
base_file = config.ConfigFile(
|
base_file = config.ConfigFile(
|
||||||
'base.yaml',
|
'base.yaml',
|
||||||
|
@ -438,7 +438,7 @@ class ProjectTest(unittest.TestCase):
|
|||||||
{
|
{
|
||||||
'name': 'foo',
|
'name': 'foo',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'networks': ['custom']
|
'networks': {'custom': None}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
networks={'custom': {}},
|
networks={'custom': {}},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user