mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-28 23:51:23 -07: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.
|
- 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
|
- "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."
|
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.
|
- 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
|
- A value of C(all) will publish all exposed container ports to random host ports, ignoring
|
||||||
any other mappings.
|
any other mappings.
|
||||||
|
@ -736,6 +741,51 @@ def is_volume_permissions(input):
|
||||||
return True
|
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):
|
class TaskParameters(DockerBaseClass):
|
||||||
'''
|
'''
|
||||||
Access and parse module parameters
|
Access and parse module parameters
|
||||||
|
@ -1107,27 +1157,38 @@ class TaskParameters(DockerBaseClass):
|
||||||
|
|
||||||
binds = {}
|
binds = {}
|
||||||
for port in self.published_ports:
|
for port in self.published_ports:
|
||||||
parts = str(port).split(':')
|
parts = split_colon_ipv6(str(port), self.client.module)
|
||||||
container_port = parts[-1]
|
container_port = parts[-1]
|
||||||
if '/' not in container_port:
|
protocol = ''
|
||||||
container_port = int(parts[-1])
|
if '/' in container_port:
|
||||||
|
container_port, protocol = parts[-1].split('/')
|
||||||
|
container_ports = parse_port_range(container_port, self.client.module)
|
||||||
|
|
||||||
p_len = len(parts)
|
p_len = len(parts)
|
||||||
if p_len == 1:
|
if p_len == 1:
|
||||||
bind = (default_ip,)
|
port_binds = len(container_ports) * [(default_ip,)]
|
||||||
elif p_len == 2:
|
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:
|
elif p_len == 3:
|
||||||
bind = (parts[0], int(parts[1])) if parts[1] else (parts[0],)
|
# 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]):
|
||||||
if container_port in binds:
|
self.fail(('Bind addresses for published ports must be IPv4 or IPv6 addresses, not hostnames. '
|
||||||
old_bind = binds[container_port]
|
'Use the dig lookup to resolve hostnames. (Found hostname: {0})').format(parts[0]))
|
||||||
if isinstance(old_bind, list):
|
if parts[1]:
|
||||||
old_bind.append(bind)
|
port_binds = [(parts[0], port) for port in parse_port_range(parts[1], self.client.module)]
|
||||||
else:
|
else:
|
||||||
binds[container_port] = [binds[container_port], bind]
|
port_binds = len(container_ports) * [(parts[0],)]
|
||||||
else:
|
|
||||||
binds[container_port] = bind
|
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
|
return binds
|
||||||
|
|
||||||
def _get_volume_binds(self, volumes):
|
def _get_volume_binds(self, volumes):
|
||||||
|
|
|
@ -92,3 +92,126 @@
|
||||||
- published_ports_3 is changed
|
- published_ports_3 is changed
|
||||||
- published_ports_4 is not changed
|
- published_ports_4 is not changed
|
||||||
- published_ports_5 is 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…
Add table
Add a link
Reference in a new issue