mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-26 12:21:26 -07:00
[docker_network] add ipv6 support (#47492)
* [docker_network] add ipv6 support * docker_network: review ipam_options * docker_network: fix requirements * docker_network: fix deprecation notice * docker_network: add minimum docker version change * docker_network: remove trailing whitespace * docker_network: revert rename of network_four #discussion_r228707101 * docker_network: refactor IPAM config comparison #discussion_r228707255, #discussion_r228707280 * docker_network: correct spelling of IPv4 and IPv6 #discussion_r228707114, #discussion_r228707138 * docker_network: manually remove networks #discussion_r228709051 * docker_network: refactor enable_ipv6 condition #discussion_r228707317 * docker_network: add mutually_exclusive #discussion_r228707185 * docker_network: fix iprange #discussion_r228709072 * docker_network: add auxiliary addresses in examples and tests * docker_network: link to docker docs #discussion_r228707018 * docker_network: remove list default #discussion_r228707060, #discussion_r228709091 * docker_network: introduce params syntax for create_network() #discussion_r228709031 * docker_network: beautify code * docker_network: resolve change requests * docker_network: add yaml header * docker_networking: fix get_ip_version * docker_network: extend CIDR test * docker_network: use backported unittest2 for python 2.6 * docker_network: migrate unittest to pytest
This commit is contained in:
parent
80ca779aa7
commit
00bab2d24d
6 changed files with 386 additions and 44 deletions
|
@ -62,6 +62,14 @@ options:
|
|||
aliases:
|
||||
- incremental
|
||||
|
||||
enable_ipv6:
|
||||
version_added: 2.8
|
||||
description:
|
||||
- Enable IPv6 networking.
|
||||
type: bool
|
||||
default: null
|
||||
required: false
|
||||
|
||||
ipam_driver:
|
||||
description:
|
||||
- Specify an IPAM driver.
|
||||
|
@ -69,6 +77,18 @@ options:
|
|||
ipam_options:
|
||||
description:
|
||||
- Dictionary of IPAM options.
|
||||
- Deprecated in 2.8, will be removed in 2.12. Use parameter ``ipam_config`` instead. In Docker 1.10.0, IPAM
|
||||
options were introduced (see L(here,https://github.com/moby/moby/pull/17316)). This module parameter addresses
|
||||
the IPAM config not the newly introduced IPAM options.
|
||||
|
||||
ipam_config:
|
||||
version_added: 2.8
|
||||
description:
|
||||
- List of IPAM config blocks. Consult
|
||||
L(Docker docs,https://docs.docker.com/compose/compose-file/compose-file-v2/#ipam) for valid options and values.
|
||||
type: list
|
||||
default: null
|
||||
required: false
|
||||
|
||||
state:
|
||||
description:
|
||||
|
@ -104,7 +124,7 @@ author:
|
|||
|
||||
requirements:
|
||||
- "python >= 2.6"
|
||||
- "docker-py >= 1.9.0"
|
||||
- "docker-py >= 1.10.0"
|
||||
- "Please note that the L(docker-py,https://pypi.org/project/docker-py/) Python
|
||||
module has been superseded by L(docker,https://pypi.org/project/docker/)
|
||||
(see L(here,https://github.com/docker/docker-py/issues/1310) for details).
|
||||
|
@ -113,7 +133,7 @@ requirements:
|
|||
be installed at the same time. Also note that when both modules are installed
|
||||
and one of them is uninstalled, the other might no longer function and a
|
||||
reinstall of it is required."
|
||||
- "The docker server >= 1.9.0"
|
||||
- "The docker server >= 1.10.0"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
@ -141,15 +161,37 @@ EXAMPLES = '''
|
|||
- container_a
|
||||
appends: yes
|
||||
|
||||
- name: Create a network with options
|
||||
- name: Create a network with driver options
|
||||
docker_network:
|
||||
name: network_two
|
||||
driver_options:
|
||||
com.docker.network.bridge.name: net2
|
||||
ipam_options:
|
||||
subnet: '172.3.26.0/16'
|
||||
gateway: 172.3.26.1
|
||||
iprange: '192.168.1.0/24'
|
||||
|
||||
- name: Create a network with custom IPAM config
|
||||
docker_network:
|
||||
name: network_three
|
||||
ipam_config:
|
||||
- subnet: 172.3.27.0/24
|
||||
gateway: 172.3.27.2
|
||||
iprange: 172.3.27.0/26
|
||||
aux_addresses:
|
||||
host1: 172.3.27.3
|
||||
host2: 172.3.27.4
|
||||
|
||||
- name: Create a network with IPv6 IPAM config
|
||||
docker_network:
|
||||
name: network_ipv6_one
|
||||
enable_ipv6: yes
|
||||
ipam_config:
|
||||
- subnet: fdd1:ac8c:0557:7ce1::/64
|
||||
|
||||
- name: Create a network with IPv6 and custom IPv4 IPAM config
|
||||
docker_network:
|
||||
name: network_ipv6_two
|
||||
enable_ipv6: yes
|
||||
ipam_config:
|
||||
- subnet: 172.4.27.0/24
|
||||
- subnet: fdd1:ac8c:0557:7ce2::/64
|
||||
|
||||
- name: Delete a network, disconnecting all containers
|
||||
docker_network:
|
||||
|
@ -166,6 +208,8 @@ facts:
|
|||
sample: {}
|
||||
'''
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.docker_common import AnsibleDockerClient, DockerBaseClass, HAS_DOCKER_PY_2, HAS_DOCKER_PY_3
|
||||
|
||||
try:
|
||||
|
@ -189,10 +233,12 @@ class TaskParameters(DockerBaseClass):
|
|||
self.driver_options = None
|
||||
self.ipam_driver = None
|
||||
self.ipam_options = None
|
||||
self.ipam_config = None
|
||||
self.appends = None
|
||||
self.force = None
|
||||
self.internal = None
|
||||
self.debug = None
|
||||
self.enable_ipv6 = None
|
||||
|
||||
for key, value in client.module.params.items():
|
||||
setattr(self, key, value)
|
||||
|
@ -202,6 +248,26 @@ def container_names_in_network(network):
|
|||
return [c['Name'] for c in network['Containers'].values()] if network['Containers'] else []
|
||||
|
||||
|
||||
CIDR_IPV4 = re.compile(r'^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$')
|
||||
CIDR_IPV6 = re.compile(r'^[0-9a-fA-F:]+/([0-9]|[1-9][0-9]|1[0-2][0-9])$')
|
||||
|
||||
|
||||
def get_ip_version(cidr):
|
||||
"""Gets the IP version of a CIDR string
|
||||
|
||||
:param cidr: Valid CIDR
|
||||
:type cidr: str
|
||||
:return: ``ipv4`` or ``ipv6``
|
||||
:rtype: str
|
||||
:raises ValueError: If ``cidr`` is not a valid CIDR
|
||||
"""
|
||||
if CIDR_IPV4.match(cidr):
|
||||
return 'ipv4'
|
||||
elif CIDR_IPV6.match(cidr):
|
||||
return 'ipv6'
|
||||
raise ValueError('"{0}" is not a valid CIDR'.format(cidr))
|
||||
|
||||
|
||||
class DockerNetworkManager(object):
|
||||
|
||||
def __init__(self, client):
|
||||
|
@ -219,6 +285,9 @@ class DockerNetworkManager(object):
|
|||
if not self.parameters.connected and self.existing_network:
|
||||
self.parameters.connected = container_names_in_network(self.existing_network)
|
||||
|
||||
if self.parameters.ipam_options:
|
||||
self.parameters.ipam_config = [self.parameters.ipam_options]
|
||||
|
||||
state = self.parameters.state
|
||||
if state == 'present':
|
||||
self.present()
|
||||
|
@ -253,29 +322,41 @@ class DockerNetworkManager(object):
|
|||
if not (key in net['Options']) or value != net['Options'][key]:
|
||||
different = True
|
||||
differences.append('driver_options.%s' % key)
|
||||
|
||||
if self.parameters.ipam_driver:
|
||||
if not net.get('IPAM') or net['IPAM']['Driver'] != self.parameters.ipam_driver:
|
||||
different = True
|
||||
differences.append('ipam_driver')
|
||||
if self.parameters.ipam_options:
|
||||
if not net.get('IPAM') or not net['IPAM'].get('Config'):
|
||||
|
||||
if self.parameters.ipam_config is not None and self.parameters.ipam_config:
|
||||
if not net.get('IPAM') or not net['IPAM']['Config']:
|
||||
different = True
|
||||
differences.append('ipam_options')
|
||||
differences.append('ipam_config')
|
||||
else:
|
||||
for key, value in self.parameters.ipam_options.items():
|
||||
camelkey = None
|
||||
for net_key in net['IPAM']['Config'][0]:
|
||||
if key == net_key.lower():
|
||||
camelkey = net_key
|
||||
break
|
||||
if not camelkey:
|
||||
# key not found
|
||||
different = True
|
||||
differences.append('ipam_options.%s' % key)
|
||||
elif net['IPAM']['Config'][0].get(camelkey) != value:
|
||||
# key has different value
|
||||
different = True
|
||||
differences.append('ipam_options.%s' % key)
|
||||
for idx, ipam_config in enumerate(self.parameters.ipam_config):
|
||||
net_config = dict()
|
||||
try:
|
||||
ip_version = get_ip_version(ipam_config['subnet'])
|
||||
for net_ipam_config in net['IPAM']['Config']:
|
||||
if ip_version == get_ip_version(net_ipam_config['Subnet']):
|
||||
net_config = net_ipam_config
|
||||
except ValueError as e:
|
||||
self.client.fail(str(e))
|
||||
|
||||
for key, value in ipam_config.items():
|
||||
camelkey = None
|
||||
for net_key in net_config:
|
||||
if key == net_key.lower():
|
||||
camelkey = net_key
|
||||
break
|
||||
if not camelkey or net_config.get(camelkey) != value:
|
||||
different = True
|
||||
differences.append('ipam_config[%s].%s' % (idx, key))
|
||||
|
||||
if self.parameters.enable_ipv6 is not None and self.parameters.enable_ipv6 != net.get('EnableIPv6', False):
|
||||
different = True
|
||||
differences.append('enable_ipv6')
|
||||
|
||||
if self.parameters.internal is not None:
|
||||
if self.parameters.internal:
|
||||
if not net.get('Internal'):
|
||||
|
@ -289,26 +370,33 @@ class DockerNetworkManager(object):
|
|||
|
||||
def create_network(self):
|
||||
if not self.existing_network:
|
||||
params = dict(
|
||||
driver=self.parameters.driver,
|
||||
options=self.parameters.driver_options,
|
||||
)
|
||||
|
||||
ipam_pools = []
|
||||
if self.parameters.ipam_options:
|
||||
if HAS_DOCKER_PY_2 or HAS_DOCKER_PY_3:
|
||||
ipam_pools.append(IPAMPool(**self.parameters.ipam_options))
|
||||
else:
|
||||
ipam_pools.append(utils.create_ipam_pool(**self.parameters.ipam_options))
|
||||
if self.parameters.ipam_config:
|
||||
for ipam_pool in self.parameters.ipam_config:
|
||||
if HAS_DOCKER_PY_2 or HAS_DOCKER_PY_3:
|
||||
ipam_pools.append(IPAMPool(**ipam_pool))
|
||||
else:
|
||||
ipam_pools.append(utils.create_ipam_pool(**ipam_pool))
|
||||
|
||||
if HAS_DOCKER_PY_2 or HAS_DOCKER_PY_3:
|
||||
ipam_config = IPAMConfig(driver=self.parameters.ipam_driver,
|
||||
pool_configs=ipam_pools)
|
||||
params['ipam'] = IPAMConfig(driver=self.parameters.ipam_driver,
|
||||
pool_configs=ipam_pools)
|
||||
else:
|
||||
ipam_config = utils.create_ipam_config(driver=self.parameters.ipam_driver,
|
||||
pool_configs=ipam_pools)
|
||||
params['ipam'] = utils.create_ipam_config(driver=self.parameters.ipam_driver,
|
||||
pool_configs=ipam_pools)
|
||||
|
||||
if self.parameters.enable_ipv6 is not None:
|
||||
params['enable_ipv6'] = self.parameters.enable_ipv6
|
||||
if self.parameters.internal is not None:
|
||||
params['internal'] = self.parameters.internal
|
||||
|
||||
if not self.check_mode:
|
||||
resp = self.client.create_network(self.parameters.network_name,
|
||||
driver=self.parameters.driver,
|
||||
options=self.parameters.driver_options,
|
||||
ipam=ipam_config,
|
||||
internal=self.parameters.internal)
|
||||
resp = self.client.create_network(self.parameters.network_name, **params)
|
||||
|
||||
self.existing_network = self.client.inspect_network(resp['Id'])
|
||||
self.results['actions'].append("Created network %s with driver %s" % (self.parameters.network_name, self.parameters.driver))
|
||||
|
@ -393,17 +481,24 @@ def main():
|
|||
driver_options=dict(type='dict', default={}),
|
||||
force=dict(type='bool', default=False),
|
||||
appends=dict(type='bool', default=False, aliases=['incremental']),
|
||||
ipam_driver=dict(type='str', default=None),
|
||||
ipam_options=dict(type='dict', default={}),
|
||||
internal=dict(type='bool', default=None),
|
||||
ipam_driver=dict(type='str'),
|
||||
ipam_options=dict(type='dict', default={}, removed_in_version='2.12'),
|
||||
ipam_config=dict(type='list', elements='dict'),
|
||||
enable_ipv6=dict(type='bool'),
|
||||
internal=dict(type='bool'),
|
||||
debug=dict(type='bool', default=False)
|
||||
)
|
||||
|
||||
mutually_exclusive = [
|
||||
('ipam_config', 'ipam_options')
|
||||
]
|
||||
|
||||
client = AnsibleDockerClient(
|
||||
argument_spec=argument_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True,
|
||||
min_docker_version='1.9.0'
|
||||
# "The docker server >= 1.9.0"
|
||||
min_docker_version='1.10.0'
|
||||
# "The docker server >= 1.10.0"
|
||||
)
|
||||
|
||||
cm = DockerNetworkManager(client)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue