mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
docker_container: add port range and IPv6 support for published_ports (#46596)
* Allow port ranges. * Adding IPv6 support for published_ports. * Die when hostname is passed instead of IP address. * Added changelog.
This commit is contained in:
parent
ccfa6ff011
commit
a74774488d
3 changed files with 200 additions and 14 deletions
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- "docker_container - published_ports now supports port ranges, IPv6 addresses, and no longer accepts hostnames, which were never used correctly anyway."
|
|
@ -301,6 +301,11 @@ options:
|
|||
- List of ports to publish from the container to the host.
|
||||
- "Use docker CLI syntax: C(8000), C(9000:8000), or C(0.0.0.0:9000:8000), where 8000 is a
|
||||
container port, 9000 is a host port, and 0.0.0.0 is a host interface."
|
||||
- Port ranges can be used for source and destination ports. If two ranges with
|
||||
different lengths are specified, the shorter range will be used.
|
||||
- "Bind addresses must be either IPv4 or IPv6 addresses. Hostnames are I(not) allowed. This
|
||||
is different from the C(docker) command line utility. Use the L(dig lookup,../lookup/dig.html)
|
||||
to resolve hostnames."
|
||||
- Container ports must be exposed either in the Dockerfile or via the C(expose) option.
|
||||
- A value of C(all) will publish all exposed container ports to random host ports, ignoring
|
||||
any other mappings.
|
||||
|
@ -736,6 +741,51 @@ def is_volume_permissions(input):
|
|||
return True
|
||||
|
||||
|
||||
def parse_port_range(range_or_port, module):
|
||||
'''
|
||||
Parses a string containing either a single port or a range of ports.
|
||||
|
||||
Returns a list of integers for each port in the list.
|
||||
'''
|
||||
if '-' in range_or_port:
|
||||
start, end = [int(port) for port in range_or_port.split('-')]
|
||||
if end < start:
|
||||
module.fail_json(msg='Invalid port range: {0}'.format(range_or_port))
|
||||
return list(range(start, end + 1))
|
||||
else:
|
||||
return [int(range_or_port)]
|
||||
|
||||
|
||||
def split_colon_ipv6(input, module):
|
||||
'''
|
||||
Split string by ':', while keeping IPv6 addresses in square brackets in one component.
|
||||
'''
|
||||
if '[' not in input:
|
||||
return input.split(':')
|
||||
start = 0
|
||||
result = []
|
||||
while start < len(input):
|
||||
i = input.find('[', start)
|
||||
if i < 0:
|
||||
result.extend(input[start:].split(':'))
|
||||
break
|
||||
j = input.find(']', i)
|
||||
if j < 0:
|
||||
module.fail_json(msg='Cannot find closing "]" in input "{0}" for opening "[" at index {1}!'.format(input, i + 1))
|
||||
result.extend(input[start:i].split(':'))
|
||||
k = input.find(':', j)
|
||||
if k < 0:
|
||||
result[-1] += input[i:]
|
||||
start = len(input)
|
||||
else:
|
||||
result[-1] += input[i:k]
|
||||
if k == len(input):
|
||||
result.append('')
|
||||
break
|
||||
start = k + 1
|
||||
return result
|
||||
|
||||
|
||||
class TaskParameters(DockerBaseClass):
|
||||
'''
|
||||
Access and parse module parameters
|
||||
|
@ -1107,27 +1157,38 @@ class TaskParameters(DockerBaseClass):
|
|||
|
||||
binds = {}
|
||||
for port in self.published_ports:
|
||||
parts = str(port).split(':')
|
||||
parts = split_colon_ipv6(str(port), self.client.module)
|
||||
container_port = parts[-1]
|
||||
if '/' not in container_port:
|
||||
container_port = int(parts[-1])
|
||||
protocol = ''
|
||||
if '/' in container_port:
|
||||
container_port, protocol = parts[-1].split('/')
|
||||
container_ports = parse_port_range(container_port, self.client.module)
|
||||
|
||||
p_len = len(parts)
|
||||
if p_len == 1:
|
||||
bind = (default_ip,)
|
||||
port_binds = len(container_ports) * [(default_ip,)]
|
||||
elif p_len == 2:
|
||||
bind = (default_ip, int(parts[0]))
|
||||
port_binds = [(default_ip, port) for port in parse_port_range(parts[0], self.client.module)]
|
||||
elif p_len == 3:
|
||||
bind = (parts[0], int(parts[1])) if parts[1] else (parts[0],)
|
||||
|
||||
if container_port in binds:
|
||||
old_bind = binds[container_port]
|
||||
if isinstance(old_bind, list):
|
||||
old_bind.append(bind)
|
||||
# We only allow IPv4 and IPv6 addresses for the bind address
|
||||
if not re.match(r'^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$', parts[0]) and not re.match(r'^\[[0-9a-fA-F:]+\]$', parts[0]):
|
||||
self.fail(('Bind addresses for published ports must be IPv4 or IPv6 addresses, not hostnames. '
|
||||
'Use the dig lookup to resolve hostnames. (Found hostname: {0})').format(parts[0]))
|
||||
if parts[1]:
|
||||
port_binds = [(parts[0], port) for port in parse_port_range(parts[1], self.client.module)]
|
||||
else:
|
||||
binds[container_port] = [binds[container_port], bind]
|
||||
else:
|
||||
binds[container_port] = bind
|
||||
port_binds = len(container_ports) * [(parts[0],)]
|
||||
|
||||
for bind, container_port in zip(port_binds, container_ports):
|
||||
idx = '{0}/{1}'.format(container_port, protocol) if protocol else container_port
|
||||
if idx in binds:
|
||||
old_bind = binds[idx]
|
||||
if isinstance(old_bind, list):
|
||||
old_bind.append(bind)
|
||||
else:
|
||||
binds[idx] = [old_bind, bind]
|
||||
else:
|
||||
binds[idx] = bind
|
||||
return binds
|
||||
|
||||
def _get_volume_binds(self, volumes):
|
||||
|
|
|
@ -92,3 +92,126 @@
|
|||
- published_ports_3 is changed
|
||||
- published_ports_4 is not changed
|
||||
- published_ports_5 is changed
|
||||
|
||||
####################################################################
|
||||
## published_ports: port range #####################################
|
||||
####################################################################
|
||||
|
||||
- name: published_ports -- port range
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
exposed_ports:
|
||||
- 8080
|
||||
- 5000-5040
|
||||
published_ports:
|
||||
- 8080:8080
|
||||
- 5000-5040:5000-5040
|
||||
stop_timeout: 1
|
||||
register: published_ports_1
|
||||
|
||||
- name: published_ports -- port range (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
exposed_ports:
|
||||
- 8080
|
||||
- 5000-5040
|
||||
published_ports:
|
||||
- 8080:8080
|
||||
- 5000-5040:5000-5040
|
||||
stop_timeout: 1
|
||||
register: published_ports_2
|
||||
|
||||
- name: published_ports -- port range (different range)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
exposed_ports:
|
||||
- 8080
|
||||
- 5000-5040
|
||||
published_ports:
|
||||
- 8080:8080
|
||||
- 5010-5050:5010-5050
|
||||
stop_timeout: 1
|
||||
register: published_ports_3
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
stop_timeout: 1
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- published_ports_1 is changed
|
||||
- published_ports_2 is not changed
|
||||
- published_ports_3 is changed
|
||||
|
||||
####################################################################
|
||||
## published_ports: IPv6 addresses #################################
|
||||
####################################################################
|
||||
|
||||
- name: published_ports -- IPv6
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
published_ports:
|
||||
- "[::1]:8080:8080"
|
||||
stop_timeout: 1
|
||||
register: published_ports_1
|
||||
|
||||
- name: published_ports -- IPv6 (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
published_ports:
|
||||
- "[::1]:8080:8080"
|
||||
stop_timeout: 1
|
||||
register: published_ports_2
|
||||
|
||||
- name: published_ports -- IPv6 (different IP)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
published_ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
stop_timeout: 1
|
||||
register: published_ports_3
|
||||
|
||||
- name: published_ports -- IPv6 (hostname)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
published_ports:
|
||||
- localhost:8080:8080
|
||||
stop_timeout: 1
|
||||
register: published_ports_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
stop_timeout: 1
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- published_ports_1 is changed
|
||||
- published_ports_2 is not changed
|
||||
- published_ports_3 is changed
|
||||
- published_ports_4 is failed
|
||||
|
|
Loading…
Reference in a new issue