diff --git a/changelogs/fragments/66382-docker_container-port-range.yml b/changelogs/fragments/66382-docker_container-port-range.yml new file mode 100644 index 00000000000..7e504aee5f9 --- /dev/null +++ b/changelogs/fragments/66382-docker_container-port-range.yml @@ -0,0 +1,3 @@ +minor_changes: + - "docker_container - support for port ranges was adjusted to be more compatible to the ``docker`` command line utility: + a one-port container range combined with a multiple-port host range will no longer result in only the first host port be used, but the whole range being passed to Docker so that a free port in that range will be used." diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.10.rst b/docs/docsite/rst/porting_guides/porting_guide_2.10.rst index 74a8aed46b9..331b97f52e0 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.10.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.10.rst @@ -109,6 +109,7 @@ Noteworthy module changes * Junction points are no longer reported as ``islnk``, use ``isjunction`` to properly report these files. This behaviour matches the :ref:`win_stat ` * Directories no longer return a ``size``, this matches the ``stat`` and ``find`` behaviour and has been removed due to the difficulties in correctly reporting the size of a directory * :ref:`docker_container ` no longer passes information on non-anonymous volumes or binds as ``Volumes`` to the Docker daemon. This increases compatibility with the ``docker`` CLI program. Note that if you specify ``volumes: strict`` in ``comparisons``, this could cause existing containers created with docker_container from Ansible 2.9 or earlier to restart. +* :ref:`docker_container `'s support for port ranges was adjusted to be more compatible to the ``docker`` command line utility: a one-port container range combined with a multiple-port host range will no longer result in only the first host port be used, but the whole range being passed to Docker so that a free port in that range will be used. * :ref:`purefb_fs ` no longer supports the deprecated ``nfs`` option. This has been superceeded by ``nfsv3``. Plugins diff --git a/lib/ansible/modules/cloud/docker/docker_container.py b/lib/ansible/modules/cloud/docker/docker_container.py index 2c8c7df85d5..53eb5131a1e 100644 --- a/lib/ansible/modules/cloud/docker/docker_container.py +++ b/lib/ansible/modules/cloud/docker/docker_container.py @@ -644,6 +644,9 @@ options: 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. + Since Ansible 2.10, if the source port range has length 1, the port will not be assigned + to the first port of the destination range, but to a free port in that range. This is the + same behavior as for C(docker) command line utility. - "Bind addresses must be either IPv4 or IPv6 addresses. Hostnames are *not* allowed. This is different from the C(docker) command line utility. Use the L(dig lookup,../lookup/dig.html) to resolve hostnames." @@ -886,8 +889,16 @@ EXAMPLES = ''' devices: - "/dev/sda:/dev/xvda:rwm" ports: + # Publish container port 9000 as host port 8080 - "8080:9000" + # Publish container UDP port 9001 as host port 8081 on interface 127.0.0.1 - "127.0.0.1:8081:9001/udp" + # Publish container port 9002 as a random host port + - "9002" + # Publish container port 9003 as a random host port in range 8000-8100 + - "9003:8000-8100" + # Publish container ports 9010-9020 to host ports 7000-7010 + - "9010-9020:7000-7010" env: SECRET_KEY: "ssssh" # Values which might be parsed as numbers, booleans or other types by the YAML parser need to be quoted @@ -1656,7 +1667,10 @@ class TaskParameters(DockerBaseClass): if p_len == 1: port_binds = len(container_ports) * [(default_ip,)] elif p_len == 2: - port_binds = [(default_ip, port) for port in parse_port_range(parts[0], self.client)] + if len(container_ports) == 1: + port_binds = [(default_ip, parts[0])] + else: + port_binds = [(default_ip, port) for port in parse_port_range(parts[0], self.client)] elif p_len == 3: # We only allow IPv4 and IPv6 addresses for the bind address ipaddr = parts[0] @@ -1666,7 +1680,10 @@ class TaskParameters(DockerBaseClass): if re.match(r'^\[[0-9a-fA-F:]+\]$', ipaddr): ipaddr = ipaddr[1:-1] if parts[1]: - port_binds = [(ipaddr, port) for port in parse_port_range(parts[1], self.client)] + if len(container_ports) == 1: + port_binds = [(ipaddr, parts[1])] + else: + port_binds = [(ipaddr, port) for port in parse_port_range(parts[1], self.client)] else: port_binds = len(container_ports) * [(ipaddr,)] diff --git a/test/integration/targets/docker_container/tasks/tests/ports.yml b/test/integration/targets/docker_container/tasks/tests/ports.yml index 71014e11c3f..e60fb3245a6 100644 --- a/test/integration/targets/docker_container/tasks/tests/ports.yml +++ b/test/integration/targets/docker_container/tasks/tests/ports.yml @@ -2,6 +2,10 @@ - name: Registering container name set_fact: cname: "{{ cname_prefix ~ '-options' }}" + cname2: "{{ cname_prefix ~ '-options-h1' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname, cname2] }}" #################################################################### ## published_ports: all ############################################ @@ -156,6 +160,68 @@ - published_ports_2 is not changed - published_ports_3 is changed +#################################################################### +## published_ports: one-element container port range ############### +#################################################################### + +- name: published_ports -- one-element container port range + docker_container: + image: alpine:3.8 + command: '/bin/sh -c "sleep 10m"' + name: "{{ item }}" + state: started + published_ports: + - "9010-9050:9010" + force_kill: yes + loop: + - '{{ cname }}' + - '{{ cname2 }}' + register: published_ports_1 + +- name: published_ports -- one-element container port range (idempotency) + docker_container: + image: alpine:3.8 + command: '/bin/sh -c "sleep 10m"' + name: "{{ item }}" + state: started + published_ports: + - "9010-9050:9010" + force_kill: yes + loop: + - '{{ cname }}' + - '{{ cname2 }}' + register: published_ports_2 + +- name: published_ports -- one-element container port range (different range) + docker_container: + image: alpine:3.8 + command: '/bin/sh -c "sleep 10m"' + name: "{{ item }}" + state: started + published_ports: + - "9010-9051:9010" + force_kill: yes + loop: + - '{{ cname }}' + - '{{ cname2 }}' + register: published_ports_3 + +- name: cleanup + docker_container: + name: "{{ item }}" + state: absent + force_kill: yes + loop: + - '{{ cname }}' + - '{{ cname2 }}' + diff: no + +- assert: + that: + - published_ports_1 is changed + - published_ports_2 is not changed + - published_ports_3 is changed + #################################################################### ## published_ports: IPv6 addresses ################################# ####################################################################