diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/docker/tests | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/docker/tests')
401 files changed, 34928 insertions, 0 deletions
diff --git a/ansible_collections/community/docker/tests/config.yml b/ansible_collections/community/docker/tests/config.yml new file mode 100644 index 000000000..5444c9e1b --- /dev/null +++ b/ansible_collections/community/docker/tests/config.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# See template for more information: +# https://github.com/ansible/ansible/blob/devel/test/lib/ansible_test/config/config.yml +modules: + python_requires: '>= 2.7' diff --git a/ansible_collections/community/docker/tests/ee/all.yml b/ansible_collections/community/docker/tests/ee/all.yml new file mode 100644 index 000000000..907866f92 --- /dev/null +++ b/ansible_collections/community/docker/tests/ee/all.yml @@ -0,0 +1,20 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: localhost + vars: + docker_test_image_alpine: quay.io/ansible/docker-test-containers:alpine3.8 + tasks: + - name: Find all roles + find: + paths: + - "{{ (playbook_dir | default('.')) ~ '/roles' }}" + file_type: directory + depth: 1 + register: result + - name: Include all roles + include_role: + name: "{{ item }}" + loop: "{{ result.files | map(attribute='path') | map('regex_replace', '.*/', '') | sort }}" diff --git a/ansible_collections/community/docker/tests/ee/roles/current_container_facts/tasks/main.yml b/ansible_collections/community/docker/tests/ee/roles/current_container_facts/tasks/main.yml new file mode 100644 index 000000000..d5096cdd7 --- /dev/null +++ b/ansible_collections/community/docker/tests/ee/roles/current_container_facts/tasks/main.yml @@ -0,0 +1,32 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Retrieve information on current container + community.docker.current_container_facts: + register: result + +# The following two tasks are useful if we ever have to debug why this fails. + +- name: Print all Ansible facts + debug: + var: ansible_facts + +- name: Read some files + slurp: + src: "{{ item }}" + loop: + - /proc/self/cpuset + - /proc/1/cgroup + - /proc/1/environ + +- name: Print facts returned by module + debug: + var: result.ansible_facts + +- name: Validate results + assert: + that: + - ansible_module_running_in_container + - ansible_module_container_type != '' diff --git a/ansible_collections/community/docker/tests/ee/roles/docker_plain/tasks/main.yml b/ansible_collections/community/docker/tests/ee/roles/docker_plain/tasks/main.yml new file mode 100644 index 000000000..9c2be8a04 --- /dev/null +++ b/ansible_collections/community/docker/tests/ee/roles/docker_plain/tasks/main.yml @@ -0,0 +1,32 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Create random name prefix (for containers, networks, ...) +- name: Create random container name prefix + set_fact: + cname_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + +- name: Make sure image is absent + community.docker.docker_image: + name: "{{ docker_test_image_alpine }}" + state: absent + +- name: Make sure image is pulled + community.docker.docker_image: + name: "{{ docker_test_image_alpine }}" + source: pull + +- name: Start container + community.docker.docker_container: + name: "{{ cname_prefix }}-1" + image: "{{ docker_test_image_alpine }}" + state: started + +- name: Remove container + community.docker.docker_container: + name: "{{ cname_prefix }}-1" + state: absent + stop_timeout: 1 + force_kill: true diff --git a/ansible_collections/community/docker/tests/ee/roles/docker_stack/tasks/main.yml b/ansible_collections/community/docker/tests/ee/roles/docker_stack/tasks/main.yml new file mode 100644 index 000000000..5d4d56986 --- /dev/null +++ b/ansible_collections/community/docker/tests/ee/roles/docker_stack/tasks/main.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Currently the docker_stack* modules are not supported in the EE since we'd need to install the Docker CLI client diff --git a/ansible_collections/community/docker/tests/integration/requirements.yml b/ansible_collections/community/docker/tests/integration/requirements.yml new file mode 100644 index 000000000..7b3e38d98 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/requirements.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +collections: +- ansible.posix +- community.internal_test_tools +- community.crypto +- community.general diff --git a/ansible_collections/community/docker/tests/integration/targets/connection/aliases b/ansible_collections/community/docker/tests/integration/targets/connection/aliases new file mode 100644 index 000000000..a02a2d61a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +hidden diff --git a/ansible_collections/community/docker/tests/integration/targets/connection/test.sh b/ansible_collections/community/docker/tests/integration/targets/connection/test.sh new file mode 100755 index 000000000..793a85dd3 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection/test.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -eux + +[ -f "${INVENTORY}" ] + +# Run connection tests with both the default and C locale. + +ansible-playbook test_connection.yml -i "${INVENTORY}" "$@" + +if ansible --version | grep ansible | grep -E ' 2\.(9|10|11|12|13)\.'; then + LC_ALL=C LANG=C ansible-playbook test_connection.yml -i "${INVENTORY}" "$@" +fi diff --git a/ansible_collections/community/docker/tests/integration/targets/connection/test_connection.yml b/ansible_collections/community/docker/tests/integration/targets/connection/test_connection.yml new file mode 100644 index 000000000..bb0a99399 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection/test_connection.yml @@ -0,0 +1,48 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: "{{ target_hosts }}" + gather_facts: false + serial: 1 + tasks: + + ### raw with unicode arg and output + + - name: raw with unicode arg and output + raw: echo 汉语 + register: command + - name: check output of raw with unicode arg and output + assert: + that: + - "'汉语' in command.stdout" + - command is changed # as of 2.2, raw should default to changed: true for consistency w/ shell/command/script modules + + ### copy local file with unicode filename and content + + - name: create local file with unicode filename and content + local_action: lineinfile dest={{ local_tmp }}-汉语/汉语.txt create=true line=汉语 + - name: remove remote file with unicode filename and content + action: "{{ action_prefix }}file path={{ remote_tmp }}-汉语/汉语.txt state=absent" + - name: create remote directory with unicode name + action: "{{ action_prefix }}file path={{ remote_tmp }}-汉语 state=directory" + - name: copy local file with unicode filename and content + action: "{{ action_prefix }}copy src={{ local_tmp }}-汉语/汉语.txt dest={{ remote_tmp }}-汉语/汉语.txt" + + ### fetch remote file with unicode filename and content + + - name: remove local file with unicode filename and content + local_action: file path={{ local_tmp }}-汉语/汉语.txt state=absent + - name: fetch remote file with unicode filename and content + fetch: src={{ remote_tmp }}-汉语/汉语.txt dest={{ local_tmp }}-汉语/汉语.txt fail_on_missing=true validate_checksum=true flat=true + + ### remove local and remote temp files + + - name: remove local temp file + local_action: file path={{ local_tmp }}-汉语 state=absent + - name: remove remote temp file + action: "{{ action_prefix }}file path={{ remote_tmp }}-汉语 state=absent" + + ### test wait_for_connection plugin + - ansible.builtin.wait_for_connection: diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker/aliases b/ansible_collections/community/docker/tests/integration/targets/connection_docker/aliases new file mode 100644 index 000000000..40eff16f5 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +skip/docker # coverage does not work if we're inside a docker container, since we cannot access this container's /tmp dir from the new container +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/connection_docker/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker/runme-connection.sh b/ansible_collections/community/docker/tests/integration/targets/connection_docker/runme-connection.sh new file mode 100755 index 000000000..f374af7ff --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker/runme-connection.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -eux + +# Connection tests for POSIX platforms use this script by linking to it from the appropriate 'connection_' target dir. +# The name of the inventory group to test is extracted from the directory name following the 'connection_' prefix. + +PYTHON="$(command -v python3 python | head -n1)" + +group=$(${PYTHON} -c \ + "from os import path; print(path.basename(path.abspath(path.dirname('$0'))).replace('connection_', ''))") + +cd ../connection + +INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \ + -e target_hosts="${group}" \ + -e action_prefix= \ + -e local_tmp=/tmp/ansible-local \ + -e remote_tmp=/tmp/ansible-remote \ + "$@" diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker/runme.sh b/ansible_collections/community/docker/tests/integration/targets/connection_docker/runme.sh new file mode 100755 index 000000000..0965c5d72 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker/runme.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# If you use another image, you possibly also need to adjust +# ansible_python_interpreter in test_connection.inventory. +source ../setup_docker/vars/main.env +IMAGE="${DOCKER_TEST_IMAGE_PYTHON3}" + +# Setup phase + +echo "Setup" +ANSIBLE_ROLES_PATH=.. ansible-playbook setup.yml + +# If docker wasn't installed, don't run the tests +if [ "$(command -v docker)" == "" ]; then + exit +fi + + +# Test phase + +CONTAINER_SUFFIX=-${RANDOM} + +DOCKER_CONTAINERS="docker-connection-test-container${CONTAINER_SUFFIX}" + +[[ -n "$DEBUG" || -n "$ANSIBLE_DEBUG" ]] && set -x + +set -euo pipefail + +cleanup() { + echo "Cleanup" + docker rm -f ${DOCKER_CONTAINERS} + echo "Shutdown" + ANSIBLE_ROLES_PATH=.. ansible-playbook shutdown.yml + echo "Done" +} + +trap cleanup INT TERM EXIT + +echo "Start containers" +for CONTAINER in ${DOCKER_CONTAINERS}; do + if [ "${ANSIBLE_TEST_COVERAGE:-}" == "" ]; then + docker run --rm --name ${CONTAINER} --detach "${IMAGE}" /bin/sh -c 'sleep 10m' + else + docker run --rm --name ${CONTAINER} --detach -v /tmp:/tmp "${IMAGE}" /bin/sh -c 'sleep 10m' + docker exec ${CONTAINER} pip3 install coverage + fi + echo ${CONTAINER} +done + +cat > test_connection.inventory << EOF +[docker] +docker-no-pipelining ansible_pipelining=false +docker-pipelining ansible_pipelining=true + +[docker:vars] +ansible_host=docker-connection-test-container${CONTAINER_SUFFIX} +ansible_connection=community.docker.docker +ansible_python_interpreter=/usr/local/bin/python3 +EOF + +echo "Run tests" +./runme-connection.sh "$@" diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker/setup.yml b/ansible_collections/community/docker/tests/integration/targets/connection_docker/setup.yml new file mode 100644 index 000000000..e522a51f0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker/setup.yml @@ -0,0 +1,14 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: localhost + connection: local + vars: + docker_skip_cleanup: true + + tasks: + - name: Setup docker + import_role: + name: setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker/shutdown.yml b/ansible_collections/community/docker/tests/integration/targets/connection_docker/shutdown.yml new file mode 100644 index 000000000..122cf059a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker/shutdown.yml @@ -0,0 +1,20 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: localhost + connection: local + vars: + docker_skip_cleanup: true + + tasks: + - name: Remove docker packages + action: "{{ ansible_facts.pkg_mgr }}" + args: + name: + - docker + - docker-ce + - docker-ce-cli + state: absent + when: not docker_skip_cleanup diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/aliases b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/aliases new file mode 100644 index 000000000..40eff16f5 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +skip/docker # coverage does not work if we're inside a docker container, since we cannot access this container's /tmp dir from the new container +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/runme-connection.sh b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/runme-connection.sh new file mode 100755 index 000000000..f374af7ff --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/runme-connection.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -eux + +# Connection tests for POSIX platforms use this script by linking to it from the appropriate 'connection_' target dir. +# The name of the inventory group to test is extracted from the directory name following the 'connection_' prefix. + +PYTHON="$(command -v python3 python | head -n1)" + +group=$(${PYTHON} -c \ + "from os import path; print(path.basename(path.abspath(path.dirname('$0'))).replace('connection_', ''))") + +cd ../connection + +INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \ + -e target_hosts="${group}" \ + -e action_prefix= \ + -e local_tmp=/tmp/ansible-local \ + -e remote_tmp=/tmp/ansible-remote \ + "$@" diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/runme.sh b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/runme.sh new file mode 100755 index 000000000..893b019ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/runme.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# If you use another image, you possibly also need to adjust +# ansible_python_interpreter in test_connection.inventory. +source ../setup_docker/vars/main.env +IMAGE="${DOCKER_TEST_IMAGE_PYTHON3}" + +# Setup phase + +echo "Setup" +ANSIBLE_ROLES_PATH=.. ansible-playbook setup.yml + +# If docker wasn't installed, don't run the tests +if [ "$(command -v docker)" == "" ]; then + exit +fi + + +# Test phase + +CONTAINER_SUFFIX=-${RANDOM} + +DOCKER_CONTAINERS="docker-connection-test-container${CONTAINER_SUFFIX}" + +[[ -n "$DEBUG" || -n "$ANSIBLE_DEBUG" ]] && set -x + +set -euo pipefail + +cleanup() { + echo "Cleanup" + docker rm -f ${DOCKER_CONTAINERS} + echo "Shutdown" + ANSIBLE_ROLES_PATH=.. ansible-playbook shutdown.yml + echo "Done" +} + +trap cleanup INT TERM EXIT + +echo "Start containers" +for CONTAINER in ${DOCKER_CONTAINERS}; do + if [ "${ANSIBLE_TEST_COVERAGE:-}" == "" ]; then + docker run --rm --name ${CONTAINER} --detach "${IMAGE}" /bin/sh -c 'sleep 10m' + else + docker run --rm --name ${CONTAINER} --detach -v /tmp:/tmp "${IMAGE}" /bin/sh -c 'sleep 10m' + docker exec ${CONTAINER} pip3 install coverage + fi + echo ${CONTAINER} +done + +cat > test_connection.inventory << EOF +[docker_api] +docker_api-no-pipelining ansible_pipelining=false +docker_api-pipelining ansible_pipelining=true + +[docker_api:vars] +ansible_host=docker-connection-test-container${CONTAINER_SUFFIX} +ansible_connection=community.docker.docker_api +ansible_python_interpreter=/usr/local/bin/python3 +EOF + +echo "Run tests" +./runme-connection.sh "$@" diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/setup.yml b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/setup.yml new file mode 100644 index 000000000..e522a51f0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/setup.yml @@ -0,0 +1,14 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: localhost + connection: local + vars: + docker_skip_cleanup: true + + tasks: + - name: Setup docker + import_role: + name: setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/shutdown.yml b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/shutdown.yml new file mode 100644 index 000000000..122cf059a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_docker_api/shutdown.yml @@ -0,0 +1,20 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: localhost + connection: local + vars: + docker_skip_cleanup: true + + tasks: + - name: Remove docker packages + action: "{{ ansible_facts.pkg_mgr }}" + args: + name: + - docker + - docker-ce + - docker-ce-cli + state: absent + when: not docker_skip_cleanup diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/aliases b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/aliases new file mode 100644 index 000000000..40067d9d3 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/aliases @@ -0,0 +1,8 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/5 +skip/docker # this requires unfettered access to the container host +skip/rhel7.9 # nsenter does not work out of the box +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/runme-connection.sh b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/runme-connection.sh new file mode 100755 index 000000000..f374af7ff --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/runme-connection.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -eux + +# Connection tests for POSIX platforms use this script by linking to it from the appropriate 'connection_' target dir. +# The name of the inventory group to test is extracted from the directory name following the 'connection_' prefix. + +PYTHON="$(command -v python3 python | head -n1)" + +group=$(${PYTHON} -c \ + "from os import path; print(path.basename(path.abspath(path.dirname('$0'))).replace('connection_', ''))") + +cd ../connection + +INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \ + -e target_hosts="${group}" \ + -e action_prefix= \ + -e local_tmp=/tmp/ansible-local \ + -e remote_tmp=/tmp/ansible-remote \ + "$@" diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/runme.sh b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/runme.sh new file mode 100755 index 000000000..eebbb6a39 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/runme.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -euo pipefail + +[[ -n "${DEBUG:-}" || -n "${ANSIBLE_DEBUG:-}" ]] && set -x + +readonly IMAGE="quay.io/ansible/ansible-runner:devel" +readonly PYTHON="$(command -v python3 python | head -n1)" + +# Determine collection root +COLLECTION_ROOT=./ +while true; do + if [ -e ${COLLECTION_ROOT}galaxy.yml ] || [ -e ${COLLECTION_ROOT}MANIFEST.json ]; then + break + fi + COLLECTION_ROOT="${COLLECTION_ROOT}../" +done +readonly COLLECTION_ROOT="$(cd ${COLLECTION_ROOT} ; pwd)" + +# Setup phase +echo "Setup" +ANSIBLE_ROLES_PATH=.. ansible-playbook setup.yml + +# If docker wasn't installed, don't run the tests +if [ "$(command -v docker)" == "" ]; then + exit +fi + +cleanup() { + echo "Cleanup" + echo "Shutdown" + ANSIBLE_ROLES_PATH=.. ansible-playbook shutdown.yml + echo "Done" +} + +envs=(--env "HOME=${HOME:-}") +while IFS=$'\0' read -d '' -r line; do + key="$(echo "$line" | cut -d= -f1)" + value="$(echo "$line" | cut -d= -f2-)" + if [[ "${key}" =~ ^(ANSIBLE_|JUNIT_OUTPUT_DIR$|OUTPUT_DIR$|PYTHONPATH$) ]]; then + envs+=(--env "${key}=${value}") + fi +done < <(printenv -0) + +# Test phase +cat > test_connection.inventory << EOF +[nsenter] +nsenter-no-pipelining ansible_pipelining=false +nsenter-pipelining ansible_pipelining=true + +[nsenter:vars] +ansible_host=localhost +ansible_connection=community.docker.nsenter +ansible_host_volume_mount=/host +ansible_nsenter_pid=1 +ansible_python_interpreter=${PYTHON} +EOF + +echo "Run tests" +set -x +docker run \ + -i \ + --rm \ + --privileged \ + --pid host \ + "${envs[@]}" \ + --volume "${COLLECTION_ROOT}:${COLLECTION_ROOT}" \ + --workdir "$(pwd)" \ + "${IMAGE}" \ + ./runme-connection.sh "$@" diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/setup.yml b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/setup.yml new file mode 100644 index 000000000..e522a51f0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/setup.yml @@ -0,0 +1,14 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: localhost + connection: local + vars: + docker_skip_cleanup: true + + tasks: + - name: Setup docker + import_role: + name: setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/shutdown.yml b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/shutdown.yml new file mode 100644 index 000000000..122cf059a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_nsenter/shutdown.yml @@ -0,0 +1,20 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: localhost + connection: local + vars: + docker_skip_cleanup: true + + tasks: + - name: Remove docker packages + action: "{{ ansible_facts.pkg_mgr }}" + args: + name: + - docker + - docker-ce + - docker-ce-cli + state: absent + when: not docker_skip_cleanup diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_posix/aliases b/ansible_collections/community/docker/tests/integration/targets/connection_posix/aliases new file mode 100644 index 000000000..44561e2ff --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_posix/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +needs/target/connection +hidden diff --git a/ansible_collections/community/docker/tests/integration/targets/connection_posix/test.sh b/ansible_collections/community/docker/tests/integration/targets/connection_posix/test.sh new file mode 100755 index 000000000..f374af7ff --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/connection_posix/test.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -eux + +# Connection tests for POSIX platforms use this script by linking to it from the appropriate 'connection_' target dir. +# The name of the inventory group to test is extracted from the directory name following the 'connection_' prefix. + +PYTHON="$(command -v python3 python | head -n1)" + +group=$(${PYTHON} -c \ + "from os import path; print(path.basename(path.abspath(path.dirname('$0'))).replace('connection_', ''))") + +cd ../connection + +INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \ + -e target_hosts="${group}" \ + -e action_prefix= \ + -e local_tmp=/tmp/ansible-local \ + -e remote_tmp=/tmp/ansible-remote \ + "$@" diff --git a/ansible_collections/community/docker/tests/integration/targets/current_container_facts/aliases b/ansible_collections/community/docker/tests/integration/targets/current_container_facts/aliases new file mode 100644 index 000000000..8577a2959 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/current_container_facts/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +skip/rhel diff --git a/ansible_collections/community/docker/tests/integration/targets/current_container_facts/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/current_container_facts/tasks/main.yml new file mode 100644 index 000000000..a0d1ae79c --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/current_container_facts/tasks/main.yml @@ -0,0 +1,41 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Get facts + current_container_facts: + register: result + + # WARNING: This is not a proper test as it won't fail when the module does not work! + # To make this a proper test, we need to know the environment in which this + # test runs, which we do not know in general... + +- name: Print facts + ansible.builtin.debug: + var: result.ansible_facts + +- name: Read files + ansible.builtin.slurp: + src: '{{ item }}' + loop: + - /proc/self/cgroup + - /proc/self/cpuset + - /proc/self/mountinfo + register: slurp + ignore_errors: true + +- name: Print files + ansible.builtin.debug: + msg: |- + {{ item.content | ansible.builtin.b64decode | split(' + ') }} + loop: '{{ slurp.results }}' + loop_control: + label: '{{ item.source | default(item.item) }}' + when: item is not failed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_compose/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_compose/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_compose/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_compose/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_compose/meta/main.yml new file mode 100644 index 000000000..7f44c871d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_compose/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker_compose + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/main.yml new file mode 100644 index 000000000..d3c7eae51 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/main.yml @@ -0,0 +1,47 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Create random name prefix (for containers, networks, ...) +- name: Create random container name prefix + set_fact: + cname_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + cnames: [] + dnetworks: [] + +- debug: + msg: "Using container name prefix {{ cname_prefix }}" + +# Run the tests +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: "Make sure all containers are removed" + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + with_items: "{{ cnames }}" + diff: false + - name: "Make sure all networks are removed" + docker_network: + name: "{{ item }}" + state: absent + force: true + with_items: "{{ dnetworks }}" + when: docker_py_version is version('1.10.0', '>=') + diff: false + + when: has_docker_compose and docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run all docker_container tests!" + when: has_docker_compose and not(docker_py_version is version('3.5.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/tests/options.yml b/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/tests/options.yml new file mode 100644 index 000000000..f24403170 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/tests/options.yml @@ -0,0 +1,243 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + pname: "{{ cname_prefix }}" + cname_1: "{{ cname_prefix ~ '1' }}" + cname_2: "{{ cname_prefix ~ '2' }}" + +#################################################################### +## Profiles ######################################################## +#################################################################### + +- block: + - name: Define service + set_fact: + test_service: | + version: '3' + services: + {{ cname_1 }}: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + profiles: + - profile_1 + - profile_all + stop_grace_period: 1s + {{ cname_2 }}: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + profiles: + - profile_2 + - profile_all + stop_grace_period: 1s + test_cases: + - test_name: no services enabled + - test_name: enable 1 + profiles_value: + - profile_1 + - test_name: stop all services + profiles_value: + - profile_1 + stopped_value: true + - test_name: enable 2 + profiles_value: + - profile_2 + - test_name: stop all services + profiles_value: + - profile_2 + stopped_value: true + - test_name: enable both + profiles_value: + - profile_1 + - profile_2 + - test_name: stop all services + profiles_value: + - profile_1 + - profile_2 + stopped_value: true + - test_name: enable all + profiles_value: + - profile_all + + - name: Profiles ({{ test_case.test_name }}) + docker_compose: + project_name: "{{ pname }}" + definition: "{{ test_service | from_yaml }}" + profiles: "{{ test_case.profiles_value | default(omit) }}" + stopped: "{{ test_case.stopped_value | default(omit) }}" + state: present + register: profiles_outputs + loop: "{{ test_cases }}" + loop_control: + loop_var: test_case + + - name: Cleanup + docker_compose: + project_name: "{{ pname }}" + state: absent + definition: "{{ test_service | from_yaml }}" + + - assert: + that: + - profiles_outputs.results[0] is not changed + - profiles_outputs.results[1].services[cname_1][cname_1_name].state.running + - profiles_outputs.results[1].services[cname_2] == {} + - not profiles_outputs.results[2].services[cname_1][cname_1_name].state.running + - profiles_outputs.results[2].services[cname_2] == {} + - not profiles_outputs.results[3].services[cname_1][cname_1_name].state.running + - profiles_outputs.results[3].services[cname_2][cname_2_name].state.running + - not profiles_outputs.results[4].services[cname_1][cname_1_name].state.running + - not profiles_outputs.results[4].services[cname_2][cname_2_name].state.running + - profiles_outputs.results[5].services[cname_1][cname_1_name].state.running + - profiles_outputs.results[5].services[cname_2][cname_2_name].state.running + - not profiles_outputs.results[6].services[cname_1][cname_1_name].state.running + - not profiles_outputs.results[6].services[cname_2][cname_2_name].state.running + - profiles_outputs.results[7].services[cname_1][cname_1_name].state.running + - profiles_outputs.results[7].services[cname_2][cname_2_name].state.running + vars: + cname_1_name: "{{ pname + '_' + cname_1 + '_1' }}" + cname_2_name: "{{ pname + '_' + cname_2 + '_1' }}" + when: docker_compose_version is version('1.28.0', '>=') + +#################################################################### +## Env_file ######################################################## +#################################################################### + +- block: + - name: Define service and files + set_fact: + compose_file: "{{ remote_tmp_dir }}/docker-compose.yml" + env_file: "{{ remote_tmp_dir }}/.env" + env_sleep_cmd: sleep 10m + new_env_file: "{{ remote_tmp_dir }}/new.env" + new_env_sleep_cmd: sleep 20m + test_service: | + version: '3' + services: + {{ cname_1 }}: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "${SLEEP_CMD}"' + stop_grace_period: 1s + + - name: Define testcases + set_fact: + test_cases: + - test_name: Without env_file option + - test_name: With env_file option + env_file: "{{ new_env_file }}" + + - name: Generate compose file + ansible.builtin.copy: + content: "{{ test_service }}" + dest: "{{ compose_file }}" + + - name: Generate .env file + ansible.builtin.copy: + content: | + SLEEP_CMD="{{ env_sleep_cmd }}" + dest: "{{ env_file }}" + + - name: Generate new.env file + ansible.builtin.copy: + content: | + SLEEP_CMD="{{ new_env_sleep_cmd }}" + dest: "{{ new_env_file }}" + + - name: Env_file + docker_compose: + project_name: "{{ pname }}" + project_src: "{{ remote_tmp_dir }}" + env_file: "{{ test_case.env_file | default(omit) }}" + register: env_file_outputs + loop: "{{ test_cases }}" + loop_control: + loop_var: test_case + + - name: Cleanup + docker_compose: + project_name: "{{ pname }}" + state: absent + definition: "{{ test_service | from_yaml }}" + + - assert: + that: + - "env_sleep_cmd is in (env_file_outputs.results[0].services[cname_1][cname_1_name].cmd | join(' '))" + - "new_env_sleep_cmd is in (env_file_outputs.results[1].services[cname_1][cname_1_name].cmd | join(' '))" + vars: + cname_1_name: "{{ pname + '_' + cname_1 + '_1' }}" + cname_2_name: "{{ pname + '_' + cname_2 + '_1' }}" + + - name: Remove files + ansible.builtin.file: + path: "{{ file_path }}" + state: absent + loop_control: + loop_var: file_path + loop: + - "{{ compose_file }}" + - "{{ env_file }}" + - "{{ new_env_file }}" + when: docker_compose_version is version('1.25.0', '>=') + +#################################################################### +## Project_src ##################################################### +#################################################################### + +- name: Define service and files + set_fact: + compose_file: "{{ remote_tmp_dir }}/docker-compose.yml" + env_sleep_cmd: sleep 10m + new_env_sleep_cmd: sleep 20m + test_service: | + version: '3' + services: + {{ cname_1 }}: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c 10m' + stop_grace_period: 1s + +- name: Generate compose file + ansible.builtin.copy: + content: "{{ test_service }}" + dest: "{{ compose_file }}" + +- name: Start with project_src + docker_compose: + project_src: "{{ remote_tmp_dir }}" + register: project_src_1 + +- name: Start with project_src (idempotent) + docker_compose: + project_src: "{{ remote_tmp_dir }}" + register: project_src_2 + +- name: Stop with project_src + docker_compose: + project_src: "{{ remote_tmp_dir }}" + state: absent + register: project_src_3 + +- name: Stop with project_src (idempotent) + docker_compose: + project_src: "{{ remote_tmp_dir }}" + state: absent + register: project_src_4 + +- name: Remove files + ansible.builtin.file: + path: "{{ file_path }}" + state: absent + loop_control: + loop_var: file_path + loop: + - "{{ compose_file }}" + +- assert: + that: + - project_src_1 is changed + # - project_src_2 is not changed -- for some reason, this currently fails! + - project_src_3 is changed + - project_src_4 is not changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/tests/start-stop.yml b/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/tests/start-stop.yml new file mode 100644 index 000000000..899450717 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_compose/tasks/tests/start-stop.yml @@ -0,0 +1,233 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + pname: "{{ cname_prefix }}" + cname: "{{ cname_prefix ~ '-hi' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [pname ~ '-' ~ cname] }}" + dnetworks: "{{ dnetworks + [pname ~ '_default'] }}" + +- name: Define service + set_fact: + test_service: | + version: '3' + services: + {{ cname }}: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + stop_grace_period: 1s + test_service_mod: | + version: '3' + services: + {{ cname }}: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 15m"' + stop_grace_period: 1s + +#################################################################### +## Present ######################################################### +#################################################################### + +- name: Present (check) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + check_mode: true + register: present_1 + +- name: Present + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + register: present_2 + +- name: Present (idempotent) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + register: present_3 + +- name: Present (idempotent check) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + check_mode: true + register: present_4 + +- name: Present (changed check) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service_mod | from_yaml }}" + check_mode: true + register: present_5 + +- name: Present (changed) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service_mod | from_yaml }}" + register: present_6 + +- assert: + that: + - present_1 is changed + - present_2 is changed + - present_3 is not changed + - present_4 is not changed + - present_5 is changed + - present_6 is changed + +#################################################################### +## Absent ########################################################## +#################################################################### + +- name: Absent (check) + docker_compose: + project_name: "{{ pname }}" + state: absent + definition: "{{ test_service_mod | from_yaml }}" + check_mode: true + register: absent_1 + +- name: Absent + docker_compose: + project_name: "{{ pname }}" + state: absent + definition: "{{ test_service_mod | from_yaml }}" + register: absent_2 + +- name: Absent (idempotent) + docker_compose: + project_name: "{{ pname }}" + state: absent + definition: "{{ test_service_mod | from_yaml }}" + register: absent_3 + +- name: Absent (idempotent check) + docker_compose: + project_name: "{{ pname }}" + state: absent + definition: "{{ test_service_mod | from_yaml }}" + check_mode: true + register: absent_4 + +- assert: + that: + - absent_1 is changed + - absent_2 is changed + - absent_3 is not changed + - absent_4 is not changed + +#################################################################### +## Stopping and starting ########################################### +#################################################################### + +- name: Present stopped (check) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + stopped: true + check_mode: true + register: present_1 + +- name: Present stopped + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + stopped: true + register: present_2 + +- name: Present stopped (idempotent) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + stopped: true + register: present_3 + +- name: Present stopped (idempotent check) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + stopped: true + check_mode: true + register: present_4 + +- name: Started (check) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + check_mode: true + register: started_1 + +- name: Started + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + register: started_2 + +- name: Started (idempotent) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + register: started_3 + +- name: Started (idempotent check) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + check_mode: true + register: started_4 + +- name: Stopped (check) + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + stopped: true + check_mode: true + register: stopped_1 + +- name: Stopped + docker_compose: + project_name: "{{ pname }}" + state: present + definition: "{{ test_service | from_yaml }}" + stopped: true + register: stopped_2 + +- name: Cleanup + docker_compose: + project_name: "{{ pname }}" + state: absent + definition: "{{ test_service | from_yaml }}" + +- assert: + that: + - present_1 is changed + - present_2 is changed + - present_3 is not changed + - present_4 is not changed + - started_1 is changed + - started_2 is changed + - started_3 is not changed + - started_4 is not changed + - stopped_1 is changed + - stopped_2 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_config/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_config/aliases new file mode 100644 index 000000000..fc581d544 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_config/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/3 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_config/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_config/meta/main.yml new file mode 100644 index 000000000..2650229d8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_config/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_config/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_config/tasks/main.yml new file mode 100644 index 000000000..1a713e796 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_config/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_docker_config.yml + when: docker_py_version is version('2.6.0', '>=') and docker_api_version is version('1.30', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_config tests!" + when: not(docker_py_version is version('2.6.0', '>=') and docker_api_version is version('1.30', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_config/tasks/test_docker_config.yml b/ansible_collections/community/docker/tests/integration/targets/docker_config/tasks/test_docker_config.yml new file mode 100644 index 000000000..015e80037 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_config/tasks/test_docker_config.yml @@ -0,0 +1,334 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - shell: "docker info --format '{% raw %}{{json .}}{% endraw %}' | python -m json.tool" + + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - shell: "docker info --format '{% raw %}{{json .}}{% endraw %}' | python -m json.tool" + + - name: Create a Swarm cluster + docker_swarm: + name: default + state: present + advertise_addr: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}" + + - name: Parameter name should be required + docker_config: + state: present + ignore_errors: true + register: output + + - name: Assert failure when called with no name + assert: + that: + - 'output is failed' + - 'output.msg == "missing required arguments: name"' + + - name: Test parameters + docker_config: + name: foo + state: present + ignore_errors: true + register: output + + - name: Assert failure when called with no data + assert: + that: + - 'output is failed' + - 'output.msg == "state is present but any of the following are missing: data, data_src"' + + - name: Create config + docker_config: + name: db_password + data: opensesame! + state: present + register: output + + - name: Create variable config_id + set_fact: + config_id: "{{ output.config_id }}" + + - name: Inspect config + command: "docker config inspect {{ config_id }}" + register: inspect + ignore_errors: true + + - debug: + var: inspect + + - name: Assert config creation succeeded + assert: + that: + - "'db_password' in inspect.stdout" + - "'ansible_key' in inspect.stdout" + when: inspect is not failed + - assert: + that: + - "'is too new. Maximum supported API version is' in inspect.stderr" + when: inspect is failed + + - name: Create config again + docker_config: + name: db_password + data: opensesame! + state: present + register: output + + - name: Assert create config is idempotent + assert: + that: + - output is not changed + + - name: Write config into file + copy: + dest: "{{ remote_tmp_dir }}/data" + content: |- + opensesame! + + - name: Create config again (from file) + docker_config: + name: db_password + data_src: "{{ remote_tmp_dir }}/data" + state: present + register: output + + - name: Assert create config is idempotent + assert: + that: + - output is not changed + + - name: Create config again (base64) + docker_config: + name: db_password + data: b3BlbnNlc2FtZSE= + data_is_b64: true + state: present + register: output + + - name: Assert create config (base64) is idempotent + assert: + that: + - output is not changed + + - name: Update config + docker_config: + name: db_password + data: newpassword! + state: present + register: output + + - name: Assert config was updated + assert: + that: + - output is changed + - output.config_id != config_id + + - name: Remove config + docker_config: + name: db_password + state: absent + + - name: Check that config is removed + command: "docker config inspect {{ config_id }}" + register: output + ignore_errors: true + + - name: Assert config was removed + assert: + that: + - output is failed + + - name: Remove config + docker_config: + name: db_password + state: absent + register: output + + - name: Assert remove config is idempotent + assert: + that: + - output is not changed + +# Rolling update + + - name: Create rolling config + docker_config: + name: rolling_password + data: opensesame! + rolling_versions: true + state: present + register: original_output + + - name: Create variable config_id + set_fact: + config_id: "{{ original_output.config_id }}" + + - name: Inspect config + command: "docker config inspect {{ config_id }}" + register: inspect + ignore_errors: true + + - debug: + var: inspect + + - name: Assert config creation succeeded + assert: + that: + - "'rolling_password' in inspect.stdout" + - "'ansible_key' in inspect.stdout" + - "'ansible_version' in inspect.stdout" + - original_output.config_name == 'rolling_password_v1' + when: inspect is not failed + - assert: + that: + - "'is too new. Maximum supported API version is' in inspect.stderr" + when: inspect is failed + + - name: Create config again + docker_config: + name: rolling_password + data: newpassword! + rolling_versions: true + state: present + register: new_output + + - name: Assert that new version is created + assert: + that: + - new_output is changed + - new_output.config_id != original_output.config_id + - new_output.config_name != original_output.config_name + - new_output.config_name == 'rolling_password_v2' + + - name: Remove rolling configs + docker_config: + name: rolling_password + rolling_versions: true + state: absent + + - name: Check that config is removed + command: "docker config inspect {{ original_output.config_id }}" + register: output + ignore_errors: true + + - name: Assert config was removed + assert: + that: + - output is failed + + - name: Check that config is removed + command: "docker config inspect {{ new_output.config_id }}" + register: output + ignore_errors: true + + - name: Assert config was removed + assert: + that: + - output is failed + +# template_driver tests + + - when: docker_py_version is version('5.0.3', '>=') and docker_api_version is version('1.37', '>=') + block: + + - name: Create regular config + docker_config: + name: db_password + data: opensesame! + state: present + + - name: Update config with template_driver + docker_config: + name: db_password + data: opensesame! + template_driver: golang + state: present + register: output + + - name: Assert config was updated + assert: + that: + - output is changed + + - name: Invalid template_driver + docker_config: + name: db_password + data: opensesame! + template_driver: "not a template driver" + state: present + ignore_errors: true + register: output + + - name: Assert failure when called with invalid template_driver + assert: + that: + - 'output is failed' + - 'output.msg == "value of template_driver must be one of: golang, got: not a template driver"' + + - name: Create config again + docker_config: + name: db_password + data: opensesame! + template_driver: golang + state: present + register: output + + - name: Assert create config is idempotent + assert: + that: + - output is not changed + + # data is the docker swarm's name + - name: Update config with template data + docker_config: + name: db_password + data: "{{ '{{' }} .Service.Name {{ '}}' }}" + template_driver: golang + state: present + register: output + + - name: Inspect config + command: "docker config inspect {{ output.config_id }}" + register: inspect + + - name: Show inspection result + debug: + var: inspect + + - name: Assert config creation succeeded + assert: + that: + - "'db_password' in inspect.stdout" + - "'ansible_key' in inspect.stdout" + # According to the API docs, 'Data' is "Base64-url-safe-encoded (RFC 4648) config data." + - "'\"Data\": \"e3sgLlNlcnZpY2UuTmFtZSB9fQ==\"' in inspect.stdout" + - "'Templating' in inspect.stdout" + - "'\"Name\": \"golang\"' in inspect.stdout" + + - name: Remove config + docker_config: + name: db_password + state: absent + + - name: Check that config is removed + command: "docker config inspect {{ output.config_id }}" + register: output + ignore_errors: true + + - name: Assert config was removed + assert: + that: + - output is failed + + always: + - name: Remove a Swarm cluster + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_container/aliases new file mode 100644 index 000000000..0837c7405 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/5 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/files/env-file b/ansible_collections/community/docker/tests/integration/targets/docker_container/files/env-file new file mode 100644 index 000000000..87bc9decd --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/files/env-file @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +TEST3=val3 +TEST4=val4 diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/filter_plugins/ipaddr_tools.py b/ansible_collections/community/docker/tests/integration/targets/docker_container/filter_plugins/ipaddr_tools.py new file mode 100644 index 000000000..f6840a3a4 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/filter_plugins/ipaddr_tools.py @@ -0,0 +1,21 @@ +# Copyright (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +def _normalize_ipaddr(ipaddr): + # Import when needed, to allow installation of that module in the test setup + import ipaddress + return ipaddress.ip_address(ipaddr).compressed + + +class FilterModule(object): + """ IP address and network manipulation filters """ + + def filters(self): + return { + 'normalize_ipaddr': _normalize_ipaddr, + } diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/main.yml new file mode 100644 index 000000000..9911452f9 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/main.yml @@ -0,0 +1,65 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Gather facts on controller + setup: + gather_subset: '!all' + delegate_to: localhost + delegate_facts: true + run_once: true + +- name: Make sure ipaddress is available on controller + pip: + name: ipaddress + delegate_to: localhost + when: hostvars['localhost'].ansible_facts.python.version.major < 3 + +# Create random name prefix (for containers, networks, ...) +- name: Create random container name prefix + set_fact: + cname_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + cnames: [] + inames: [] + dnetworks: [] + +- debug: + msg: "Using container name prefix {{ cname_prefix }}" + +# Run the tests +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: "Make sure all containers are removed" + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + with_items: "{{ cnames }}" + diff: false + - name: "Make sure all images are removed" + docker_image: + name: "{{ item }}" + state: absent + with_items: "{{ inames }}" + - name: "Make sure all networks are removed" + docker_network: + name: "{{ item }}" + state: absent + force: true + with_items: "{{ dnetworks }}" + diff: false + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run all docker_container tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/comparisons.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/comparisons.yml new file mode 100644 index 000000000..54f0d4a62 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/comparisons.yml @@ -0,0 +1,467 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-comparisons' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname] }}" + +#################################################################### +## value ########################################################### +#################################################################### + +- name: value + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + hostname: example.com + register: value_1 + +- name: value (change, ignore) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + hostname: example.org + force_kill: true + comparisons: + hostname: ignore + register: value_2 + +- name: value (change, strict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + hostname: example.org + force_kill: true + comparisons: + hostname: strict + register: value_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - value_1 is changed + - value_2 is not changed + - value_3 is changed + +#################################################################### +## list ############################################################ +#################################################################### + +- name: list + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_servers: + - 1.1.1.1 + - 8.8.8.8 + register: list_1 + +- name: list (change, ignore) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_servers: + - 9.9.9.9 + force_kill: true + comparisons: + dns_servers: ignore + register: list_2 + +- name: list (change, strict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_servers: + - 9.9.9.9 + force_kill: true + comparisons: + dns_servers: strict + register: list_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - list_1 is changed + - list_2 is not changed + - list_3 is changed + +#################################################################### +## set ############################################################# +#################################################################### + +- name: set + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "1010" + - "1011" + register: set_1 + +- name: set (change, ignore) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "1010" + - "1011" + - "1012" + force_kill: true + comparisons: + groups: ignore + register: set_2 + +- name: set (change, allow_more_present) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "1010" + - "1011" + - "1012" + force_kill: true + comparisons: + groups: allow_more_present + register: set_3 + +- name: set (change, allow_more_present) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "1010" + - "1012" + force_kill: true + comparisons: + groups: allow_more_present + register: set_4 + +- name: set (change, strict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "1010" + - "1012" + force_kill: true + comparisons: + groups: strict + register: set_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - set_1 is changed + - set_2 is not changed + - set_3 is changed + - set_4 is not changed + - set_5 is changed + +#################################################################### +## set(dict) ####################################################### +#################################################################### + +- name: set(dict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/random:/dev/virt-random:rwm" + - "/dev/urandom:/dev/virt-urandom:rwm" + register: set_dict_1 + +- name: set(dict) (change, ignore) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/random:/dev/virt-random:rwm" + - "/dev/urandom:/dev/virt-urandom:rwm" + - "/dev/null:/dev/virt-null:rwm" + force_kill: true + comparisons: + devices: ignore + register: set_dict_2 + +- name: set(dict) (change, allow_more_present) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/random:/dev/virt-random:rwm" + - "/dev/urandom:/dev/virt-urandom:rwm" + - "/dev/null:/dev/virt-null:rwm" + force_kill: true + comparisons: + devices: allow_more_present + register: set_dict_3 + +- name: set(dict) (change, allow_more_present) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/random:/dev/virt-random:rwm" + - "/dev/null:/dev/virt-null:rwm" + force_kill: true + comparisons: + devices: allow_more_present + register: set_dict_4 + +- name: set(dict) (change, strict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/random:/dev/virt-random:rwm" + - "/dev/null:/dev/virt-null:rwm" + force_kill: true + comparisons: + devices: strict + register: set_dict_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - set_dict_1 is changed + - set_dict_2 is not changed + - set_dict_3 is changed + - set_dict_4 is not changed + - set_dict_5 is changed + +#################################################################### +## dict ############################################################ +#################################################################### + +- name: dict + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.1: hello + ansible.test.2: world + register: dict_1 + +- name: dict (change, ignore) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.1: hello + ansible.test.2: world + ansible.test.3: ansible + force_kill: true + comparisons: + labels: ignore + register: dict_2 + +- name: dict (change, allow_more_present) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.1: hello + ansible.test.2: world + ansible.test.3: ansible + force_kill: true + comparisons: + labels: allow_more_present + register: dict_3 + +- name: dict (change, allow_more_present) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.1: hello + ansible.test.3: ansible + force_kill: true + comparisons: + labels: allow_more_present + register: dict_4 + +- name: dict (change, strict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.1: hello + ansible.test.3: ansible + force_kill: true + comparisons: + labels: strict + register: dict_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - dict_1 is changed + - dict_2 is not changed + - dict_3 is changed + - dict_4 is not changed + - dict_5 is changed + +#################################################################### +## wildcard ######################################################## +#################################################################### + +- name: Pull {{ docker_test_image_hello_world }} image to make sure wildcard_2 test succeeds + # If the image isn't there, it will pull it and return 'changed'. + docker_image: + name: "{{ docker_test_image_hello_world }}" + source: pull + +- name: wildcard + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + hostname: example.com + stop_timeout: 1 + labels: + ansible.test.1: hello + ansible.test.2: world + ansible.test.3: ansible + register: wildcard_1 + +- name: wildcard (change, ignore) + docker_container: + image: "{{ docker_test_image_hello_world }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + hostname: example.org + stop_timeout: 2 + labels: + ansible.test.1: hello + ansible.test.4: ignore + force_kill: true + comparisons: + '*': ignore + register: wildcard_2 + +- name: wildcard (change, strict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + hostname: example.org + stop_timeout: 1 + labels: + ansible.test.1: hello + ansible.test.2: world + ansible.test.3: ansible + force_kill: true + comparisons: + '*': strict + register: wildcard_3 + +- name: wildcard (no change, strict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + hostname: example.org + stop_timeout: 1 + labels: + ansible.test.1: hello + ansible.test.2: world + ansible.test.3: ansible + force_kill: true + comparisons: + '*': strict + register: wildcard_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - wildcard_1 is changed + - wildcard_2 is not changed + - wildcard_3 is changed + - wildcard_4 is not changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/compatibility.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/compatibility.yml new file mode 100644 index 000000000..265aacacb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/compatibility.yml @@ -0,0 +1,122 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-hi' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname] }}" + +#################################################################### +## container_default_behavior: compatibility ####################### +#################################################################### + +- name: Start container (check) + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + container_default_behavior: compatibility + check_mode: true + register: start_1 + +- name: Start container + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + container_default_behavior: compatibility + register: start_2 + +- name: Start container (idempotent) + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + container_default_behavior: compatibility + register: start_3 + +- name: Start container (idempotent check) + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + state: started + container_default_behavior: compatibility + check_mode: true + register: start_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - start_1 is changed + - start_2 is changed + - start_3 is not changed + - start_4 is not changed + +#################################################################### +## container_default_behavior: no_defaults ######################### +#################################################################### + +- name: Start container (check) + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + container_default_behavior: no_defaults + check_mode: true + register: start_1 + +- name: Start container + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + container_default_behavior: no_defaults + register: start_2 + +- name: Start container (idempotent) + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + container_default_behavior: no_defaults + register: start_3 + +- name: Start container (idempotent check) + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + container_default_behavior: no_defaults + check_mode: true + register: start_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - start_1 is changed + - start_2 is changed + - start_3 is not changed + - start_4 is not changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/image-ids.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/image-ids.yml new file mode 100644 index 000000000..76270c68a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/image-ids.yml @@ -0,0 +1,155 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-iid' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname] }}" + +- name: Pull images + docker_image: + name: "{{ image }}" + source: pull + loop: + - "{{ docker_test_image_hello_world }}" + - "{{ docker_test_image_alpine }}" + loop_control: + loop_var: image + +- name: Get image ID of {{ docker_test_image_hello_world }} and {{ docker_test_image_alpine }} images + docker_image_info: + name: + - "{{ docker_test_image_hello_world }}" + - "{{ docker_test_image_alpine }}" + register: image_info + +- assert: + that: + - image_info.images | length == 2 + +- name: Print image IDs + debug: + msg: "{{ docker_test_image_hello_world }}: {{ image_info.images[0].Id }}; {{ docker_test_image_alpine }}: {{ image_info.images[1].Id }}" + +- name: Create container with {{ docker_test_image_hello_world }} image via ID + docker_container: + image: "{{ image_info.images[0].Id }}" + name: "{{ cname }}" + state: present + force_kill: true + register: create_1 + +- name: Create container with {{ docker_test_image_hello_world }} image via ID (idempotent) + docker_container: + image: "{{ image_info.images[0].Id }}" + name: "{{ cname }}" + state: present + force_kill: true + register: create_2 + +- name: Create container with {{ docker_test_image_alpine }} image via ID + docker_container: + image: "{{ image_info.images[1].Id }}" + name: "{{ cname }}" + state: present + force_kill: true + register: create_3 + +- name: Create container with {{ docker_test_image_alpine }} image via ID (idempotent) + docker_container: + image: "{{ image_info.images[1].Id }}" + name: "{{ cname }}" + state: present + force_kill: true + register: create_4 + +- name: Untag image + # Image will not be deleted since the container still uses it + docker_image: + name: "{{ docker_test_image_alpine }}" + force_absent: true + state: absent + +- name: Create container with {{ docker_test_image_alpine }} image via name (check mode, will pull, same image) + docker_container: + image: "{{ docker_test_image_alpine }}" + name: "{{ cname }}" + state: present + register: create_5 + check_mode: true + +- name: Create container with {{ docker_test_image_alpine }} image via name (will pull, same image) + docker_container: + image: "{{ docker_test_image_alpine }}" + name: "{{ cname }}" + state: present + register: create_6 + +- name: Cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - create_1 is changed + - create_2 is not changed + - create_3 is changed + - create_4 is not changed + - create_5 is changed + - create_6 is changed + - create_6.container.Image == image_info.images[1].Id + - create_6.container.Id == create_4.container.Id # make sure container wasn't recreated + +- name: Create container with {{ docker_test_image_digest_base }} image via old digest + docker_container: + image: "{{ docker_test_image_digest_base }}@sha256:{{ docker_test_image_digest_v1 }}" + name: "{{ cname }}" + state: present + force_kill: true + register: digest_1 + +- name: Create container with {{ docker_test_image_digest_base }} image via old digest (idempotent) + docker_container: + image: "{{ docker_test_image_digest_base }}@sha256:{{ docker_test_image_digest_v1 }}" + name: "{{ cname }}" + state: present + force_kill: true + register: digest_2 + +- name: Create container with {{ docker_test_image_digest_base }} image via old digest (idempotent, pull) + docker_container: + image: "{{ docker_test_image_digest_base }}@sha256:{{ docker_test_image_digest_v1 }}" + name: "{{ cname }}" + pull: true + state: present + force_kill: true + register: digest_3 + +- name: Update container with {{ docker_test_image_digest_base }} image via new digest + docker_container: + image: "{{ docker_test_image_digest_base }}@sha256:{{ docker_test_image_digest_v2 }}" + name: "{{ cname }}" + state: present + force_kill: true + register: digest_4 + +- name: Cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - digest_1 is changed + - digest_2 is not changed + - digest_3 is not changed + - digest_4 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/mounts-volumes.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/mounts-volumes.yml new file mode 100644 index 000000000..3ce6691ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/mounts-volumes.yml @@ -0,0 +1,558 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-mounts' }}" + cname_h1: "{{ cname_prefix ~ '-mounts-h1' }}" + cname_h2: "{{ cname_prefix ~ '-mounts-h2' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname, cname_h1, cname_h2] }}" + +#################################################################### +## keep_volumes #################################################### +#################################################################### + +# TODO: - keep_volumes + +#################################################################### +## mounts ########################################################## +#################################################################### + +- name: mounts + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: /tmp + target: /tmp + type: bind + - source: / + target: /whatever + type: bind + read_only: false + register: mounts_1 + +- name: mounts (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: / + target: /whatever + type: bind + read_only: false + - source: /tmp + target: /tmp + type: bind + register: mounts_2 + +- name: mounts (less mounts) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: /tmp + target: /tmp + type: bind + register: mounts_3 + +- name: mounts (more mounts) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: /tmp + target: /tmp + type: bind + - source: /tmp + target: /somewhereelse + type: bind + read_only: true + force_kill: true + register: mounts_4 + +- name: mounts (different modes) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: /tmp + target: /tmp + type: bind + - source: /tmp + target: /somewhereelse + type: bind + read_only: false + force_kill: true + register: mounts_5 + +- name: mounts (endpoint collision) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: /home + target: /x + type: bind + - source: /etc + target: /x + type: bind + read_only: false + force_kill: true + register: mounts_6 + ignore_errors: true + +- name: mounts (anonymous volume) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - target: /tmp + type: volume + force_kill: true + register: mounts_7 + +- name: mounts (anonymous volume idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - target: /tmp + type: volume + force_kill: true + register: mounts_8 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - mounts_1 is changed + - mounts_2 is not changed + - mounts_3 is not changed + - mounts_4 is changed + - mounts_5 is changed + - mounts_6 is failed + - "'The mount point \"/x\" appears twice in the mounts option' == mounts_6.msg" + - mounts_7 is changed + - mounts_8 is not changed + +#################################################################### +## tmpfs ########################################################### +#################################################################### + +- name: tmpfs + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - target: /cache1 + type: tmpfs + tmpfs_mode: "1777" + tmpfs_size: "1GB" + - target: /cache2 + type: tmpfs + tmpfs_mode: "1777" + tmpfs_size: "1GB" + force_kill: true + register: tmpfs_1 + +- name: tmpfs (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - target: /cache2 + type: tmpfs + tmpfs_mode: "1777" + tmpfs_size: "1GB" + - target: /cache1 + type: tmpfs + tmpfs_mode: "1777" + tmpfs_size: "1GB" + force_kill: true + register: tmpfs_2 + +- name: tmpfs (more mounts) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - target: /cache1 + type: tmpfs + tmpfs_mode: "1777" + tmpfs_size: "1GB" + - target: /cache2 + type: tmpfs + tmpfs_mode: "1777" + tmpfs_size: "1GB" + - target: /cache3 + type: tmpfs + tmpfs_mode: "1777" + tmpfs_size: "1GB" + force_kill: true + register: tmpfs_3 + +- name: tmpfs (change mode) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - target: /cache1 + type: tmpfs + tmpfs_mode: "1700" + tmpfs_size: "1GB" + force_kill: true + register: tmpfs_4 + +- name: tmpfs (change size) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - target: /cache1 + type: tmpfs + tmpfs_mode: "1700" + tmpfs_size: "2GB" + force_kill: true + register: tmpfs_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - tmpfs_1 is changed + - tmpfs_2 is not changed + - tmpfs_3 is changed + - tmpfs_4 is changed + - tmpfs_5 is changed + +#################################################################### +## mounts + volumes ################################################ +#################################################################### + +- name: mounts + volumes + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: / + target: /whatever + type: bind + read_only: true + volumes: + - /tmp:/tmp + register: mounts_volumes_1 + +- name: mounts + volumes (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: / + target: /whatever + type: bind + read_only: true + volumes: + - /tmp:/tmp + register: mounts_volumes_2 + +- name: mounts + volumes (switching) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: /tmp + target: /tmp + type: bind + read_only: false + volumes: + - /:/whatever:ro + force_kill: true + register: mounts_volumes_3 + +- name: mounts + volumes (collision, should fail) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + mounts: + - source: /tmp + target: /tmp + type: bind + read_only: false + volumes: + - /tmp:/tmp + force_kill: true + register: mounts_volumes_4 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - mounts_volumes_1 is changed + - mounts_volumes_2 is not changed + - mounts_volumes_3 is changed + - mounts_volumes_4 is failed + - "'The mount point \"/tmp\" appears both in the volumes and mounts option' in mounts_volumes_4.msg" + +#################################################################### +## volume_driver ################################################### +#################################################################### + +- name: volume_driver + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + volume_driver: local + state: started + register: volume_driver_1 + +- name: volume_driver (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + volume_driver: local + state: started + register: volume_driver_2 + +- name: volume_driver (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + volume_driver: / + state: started + force_kill: true + register: volume_driver_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - volume_driver_1 is changed + - volume_driver_2 is not changed + - volume_driver_3 is changed + +#################################################################### +## volumes ######################################################### +#################################################################### + +- name: volumes + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes: + - "/tmp:/tmp" + - "/:/whatever:rw,z" + - "/anon:rw" + register: volumes_1 + +- name: volumes (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes: + - "/:/whatever:rw,z" + - "/tmp:/tmp" + - "/anon:rw" + register: volumes_2 + +- name: volumes (less volumes) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes: + - "/tmp:/tmp" + register: volumes_3 + +- name: volumes (more volumes) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes: + - "/tmp:/tmp" + - "/tmp:/somewhereelse:ro,Z" + force_kill: true + register: volumes_4 + +- name: volumes (different modes) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes: + - "/tmp:/tmp" + - "/tmp:/somewhereelse:ro" + force_kill: true + register: volumes_5 + +- name: volumes (collision) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes: + - "/etc:/tmp" + - "/home:/tmp:ro" + force_kill: true + register: volumes_6 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - volumes_1 is changed + - volumes_1.container.Config.Volumes | length == 1 + - volumes_1.container.Config.Volumes['/anon:rw'] | length == 0 + - volumes_2 is not changed + - volumes_3 is not changed + - volumes_4 is changed + - not volumes_4.container.Config.Volumes + - volumes_5 is changed + - volumes_6 is failed + - "'The mount point \"/tmp\" appears twice in the volumes option' in volumes_6.msg" + +#################################################################### +## volumes_from #################################################### +#################################################################### + +- name: start helpers + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ container_name }}" + state: started + volumes: + - "{{ '/tmp:/tmp' if container_name == cname_h1 else '/:/whatever:ro' }}" + loop: + - "{{ cname_h1 }}" + - "{{ cname_h2 }}" + loop_control: + loop_var: container_name + +- name: volumes_from + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes_from: "{{ cname_h1 }}" + register: volumes_from_1 + +- name: volumes_from (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes_from: "{{ cname_h1 }}" + register: volumes_from_2 + +- name: volumes_from (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + volumes_from: "{{ cname_h2 }}" + force_kill: true + register: volumes_from_3 + +- name: cleanup + docker_container: + name: "{{ container_name }}" + state: absent + force_kill: true + loop: + - "{{ cname }}" + - "{{ cname_h1 }}" + - "{{ cname_h2 }}" + loop_control: + loop_var: container_name + diff: false + +- assert: + that: + - volumes_from_1 is changed + - volumes_from_2 is not changed + - volumes_from_3 is changed + +#################################################################### +#################################################################### +#################################################################### diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/network.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/network.yml new file mode 100644 index 000000000..d6a6f0bc4 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/network.yml @@ -0,0 +1,747 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-network' }}" + cname_h1: "{{ cname_prefix ~ '-network-h1' }}" + nname_1: "{{ cname_prefix ~ '-network-1' }}" + nname_2: "{{ cname_prefix ~ '-network-2' }}" + nname_3: "{{ cname_prefix ~ '-network-3' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname, cname_h1] }}" + dnetworks: "{{ dnetworks + [nname_1, nname_2, nname_3] }}" + +- name: Create networks + docker_network: + name: "{{ network_name }}" + state: present + loop: + - "{{ nname_1 }}" + - "{{ nname_2 }}" + loop_control: + loop_var: network_name + +- set_fact: + subnet_ipv4_base: 10.{{ 16 + (240 | random) }}.{{ 16 + (240 | random) }} + subnet_ipv6_base: fdb6:feea:{{ '%0.4x:%0.4x' | format(65536 | random, 65536 | random) }} + # If netaddr would be installed on the controller, one could do: + # subnet_ipv4: "10.{{ 16 + (240 | random) }}.{{ 16 + (240 | random) }}.0/24" + # subnet_ipv6: "fdb6:feea:{{ '%0.4x:%0.4x' | format(65536 | random, 65536 | random) }}::/64" + +- set_fact: + subnet_ipv4: "{{ subnet_ipv4_base }}.0/24" + subnet_ipv6: "{{ subnet_ipv6_base }}::/64" + nname_3_ipv4_2: "{{ subnet_ipv4_base }}.2" + nname_3_ipv4_3: "{{ subnet_ipv4_base }}.3" + nname_3_ipv4_4: "{{ subnet_ipv4_base }}.4" + nname_3_ipv6_2: "{{ subnet_ipv6_base }}::2" + nname_3_ipv6_3: "{{ subnet_ipv6_base }}::3" + nname_3_ipv6_4: "{{ subnet_ipv6_base }}::4" + # If netaddr would be installed on the controller, one could do: + # nname_3_ipv4_2: "{{ subnet_ipv4 | ansible.netcommon.next_nth_usable(2) }}" + # nname_3_ipv4_3: "{{ subnet_ipv4 | ansible.netcommon.next_nth_usable(3) }}" + # nname_3_ipv4_4: "{{ subnet_ipv4 | ansible.netcommon.next_nth_usable(4) }}" + # nname_3_ipv6_2: "{{ subnet_ipv6 | ansible.netcommon.next_nth_usable(2) }}" + # nname_3_ipv6_3: "{{ subnet_ipv6 | ansible.netcommon.next_nth_usable(3) }}" + # nname_3_ipv6_4: "{{ subnet_ipv6 | ansible.netcommon.next_nth_usable(4) }}" + +- debug: + msg: "Chose random IPv4 subnet {{ subnet_ipv4 }} and random IPv6 subnet {{ subnet_ipv6 }}" + +- name: Create network with fixed IPv4 and IPv6 subnets + docker_network: + name: "{{ nname_3 }}" + enable_ipv6: true + ipam_config: + - subnet: "{{ subnet_ipv4 }}" + - subnet: "{{ subnet_ipv6 }}" + state: present + +#################################################################### +## network_mode #################################################### +#################################################################### + +- name: network_mode + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + network_mode: host + register: network_mode_1 + +- name: network_mode (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + network_mode: host + register: network_mode_2 + +- name: network_mode (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + network_mode: none + force_kill: true + register: network_mode_3 + +- name: network_mode (container mode setup) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname_h1 }}" + state: started + register: cname_h1_id + +- name: network_mode (container mode) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + network_mode: "container:{{ cname_h1_id.container.Id }}" + force_kill: true + register: network_mode_4 + +- name: network_mode (container mode idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + network_mode: "container:{{ cname_h1 }}" + register: network_mode_5 + +- name: cleanup + docker_container: + name: "{{ container_name }}" + state: absent + force_kill: true + loop: + - "{{ cname }}" + - "{{ cname_h1 }}" + loop_control: + loop_var: container_name + diff: false + +- assert: + that: + - network_mode_1 is changed + - network_mode_1.container.HostConfig.NetworkMode == 'host' + - network_mode_2 is not changed + - network_mode_2.container.HostConfig.NetworkMode == 'host' + - network_mode_3 is changed + - network_mode_3.container.HostConfig.NetworkMode == 'none' + - network_mode_4 is changed + - network_mode_4.container.HostConfig.NetworkMode == 'container:' ~ cname_h1_id.container.Id + - network_mode_5 is not changed + - network_mode_5.container.HostConfig.NetworkMode == 'container:' ~ cname_h1_id.container.Id + +#################################################################### +## networks, purge_networks for networks_cli_compatible=no ######### +#################################################################### + +- name: networks_cli_compatible=no, networks w/o purge_networks + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_1 }}" + - name: "{{ nname_2 }}" + networks_cli_compatible: false + register: networks_1 + +- name: networks_cli_compatible=no, networks w/o purge_networks + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_1 }}" + - name: "{{ nname_2 }}" + networks_cli_compatible: false + register: networks_2 + +- name: networks_cli_compatible=no, networks, purge_networks + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + comparisons: + networks: strict + networks: + - name: bridge + - name: "{{ nname_1 }}" + networks_cli_compatible: false + force_kill: true + register: networks_3 + +- name: networks_cli_compatible=no, networks, purge_networks (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + comparisons: + networks: strict + networks: + - name: "{{ nname_1 }}" + - name: bridge + networks_cli_compatible: false + register: networks_4 + +- name: networks_cli_compatible=no, networks (less networks) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: bridge + networks_cli_compatible: false + register: networks_5 + +- name: networks_cli_compatible=no, networks, purge_networks (less networks) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + comparisons: + networks: strict + networks: + - name: bridge + networks_cli_compatible: false + force_kill: true + register: networks_6 + +- name: networks_cli_compatible=no, networks, purge_networks (more networks) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + comparisons: + networks: strict + networks: + - name: bridge + - name: "{{ nname_2 }}" + networks_cli_compatible: false + force_kill: true + register: networks_7 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + # networks_1 has networks default, 'bridge', nname_1 + - networks_1 is changed + - networks_1.container.NetworkSettings.Networks | length == 3 + - nname_1 in networks_1.container.NetworkSettings.Networks + - nname_2 in networks_1.container.NetworkSettings.Networks + - "'default' in networks_1.container.NetworkSettings.Networks or 'bridge' in networks_1.container.NetworkSettings.Networks" + # networks_2 has networks default, 'bridge', nname_1 + - networks_2 is not changed + - networks_2.container.NetworkSettings.Networks | length == 3 + - nname_1 in networks_2.container.NetworkSettings.Networks + - nname_2 in networks_1.container.NetworkSettings.Networks + - "'default' in networks_1.container.NetworkSettings.Networks or 'bridge' in networks_1.container.NetworkSettings.Networks" + # networks_3 has networks 'bridge', nname_1 + - networks_3 is changed + - networks_3.container.NetworkSettings.Networks | length == 2 + - nname_1 in networks_3.container.NetworkSettings.Networks + - "'default' in networks_3.container.NetworkSettings.Networks or 'bridge' in networks_3.container.NetworkSettings.Networks" + # networks_4 has networks 'bridge', nname_1 + - networks_4 is not changed + - networks_4.container.NetworkSettings.Networks | length == 2 + - nname_1 in networks_4.container.NetworkSettings.Networks + - "'default' in networks_4.container.NetworkSettings.Networks or 'bridge' in networks_4.container.NetworkSettings.Networks" + # networks_5 has networks 'bridge', nname_1 + - networks_5 is not changed + - networks_5.container.NetworkSettings.Networks | length == 2 + - nname_1 in networks_5.container.NetworkSettings.Networks + - "'default' in networks_5.container.NetworkSettings.Networks or 'bridge' in networks_5.container.NetworkSettings.Networks" + # networks_6 has networks 'bridge' + - networks_6 is changed + - networks_6.container.NetworkSettings.Networks | length == 1 + - "'default' in networks_6.container.NetworkSettings.Networks or 'bridge' in networks_6.container.NetworkSettings.Networks" + # networks_7 has networks 'bridge', nname_2 + - networks_7 is changed + - networks_7.container.NetworkSettings.Networks | length == 2 + - nname_2 in networks_7.container.NetworkSettings.Networks + - "'default' in networks_7.container.NetworkSettings.Networks or 'bridge' in networks_7.container.NetworkSettings.Networks" + +#################################################################### +## networks for networks_cli_compatible=yes ######################## +#################################################################### + +- name: networks_cli_compatible=yes, networks specified + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_1 }}" + aliases: + - alias1 + - alias2 + - name: "{{ nname_2 }}" + networks_cli_compatible: true + register: networks_1 + +- name: networks_cli_compatible=yes, networks specified + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_1 }}" + - name: "{{ nname_2 }}" + networks_cli_compatible: true + register: networks_2 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- name: networks_cli_compatible=yes, empty networks list specified + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: [] + networks_cli_compatible: true + register: networks_3 + +- name: networks_cli_compatible=yes, empty networks list specified + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: [] + networks_cli_compatible: true + register: networks_4 + +- name: networks_cli_compatible=yes, empty networks list specified, purge_networks + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: [] + networks_cli_compatible: true + comparisons: + networks: strict + force_kill: true + register: networks_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- name: networks_cli_compatible=yes, networks not specified + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks_cli_compatible: true + force_kill: true + register: networks_6 + +- name: networks_cli_compatible=yes, networks not specified + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks_cli_compatible: true + register: networks_7 + +- name: networks_cli_compatible=yes, networks not specified, purge_networks + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks_cli_compatible: true + purge_networks: true + # To replace `purge_networks=true`, we have to specify `networks: []`: + # comparisons: + # networks: strict + # networks: [] + force_kill: true + register: networks_8 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- debug: var=networks_3 + +- assert: + that: + # networks_1 has networks nname_1, nname_2 + - networks_1 is changed + - networks_1.container.NetworkSettings.Networks | length == 2 + - nname_1 in networks_1.container.NetworkSettings.Networks + - nname_2 in networks_1.container.NetworkSettings.Networks + # networks_2 has networks nname_1, nname_2 + - networks_2 is not changed + - networks_2.container.NetworkSettings.Networks | length == 2 + - nname_1 in networks_2.container.NetworkSettings.Networks + - nname_2 in networks_1.container.NetworkSettings.Networks + # networks_3 has networks 'bridge' + - networks_3 is changed + - networks_3.container.NetworkSettings.Networks | length == 1 + - "'default' in networks_3.container.NetworkSettings.Networks or 'bridge' in networks_3.container.NetworkSettings.Networks" + # networks_4 has networks 'bridge' + - networks_4 is not changed + - networks_4.container.NetworkSettings.Networks | length == 1 + - "'default' in networks_4.container.NetworkSettings.Networks or 'bridge' in networks_4.container.NetworkSettings.Networks" + # networks_5 has no networks + - networks_5 is changed + - networks_5.container.NetworkSettings.Networks | length == 0 + # networks_6 has networks 'bridge' + - networks_6 is changed + - networks_6.container.NetworkSettings.Networks | length == 1 + - "'default' in networks_6.container.NetworkSettings.Networks or 'bridge' in networks_6.container.NetworkSettings.Networks" + # networks_7 has networks 'bridge' + - networks_7 is not changed + - networks_7.container.NetworkSettings.Networks | length == 1 + - "'default' in networks_7.container.NetworkSettings.Networks or 'bridge' in networks_7.container.NetworkSettings.Networks" + # networks_8 has no networks + - networks_8 is changed + - networks_8.container.NetworkSettings.Networks | length == 0 + +#################################################################### +## networks with comparisons ####################################### +#################################################################### + +- name: create container with one network + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_1 }}" + networks_cli_compatible: true + register: networks_1 + +- name: different networks, comparisons=ignore + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_2 }}" + networks_cli_compatible: true + comparisons: + network_mode: ignore # otherwise we'd have to set network_mode to nname_1 + networks: ignore + register: networks_2 + +- name: less networks, comparisons=ignore + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: [] + networks_cli_compatible: true + comparisons: + networks: ignore + register: networks_3 + +- name: less networks, comparisons=allow_more_present + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: [] + networks_cli_compatible: true + comparisons: + networks: allow_more_present + register: networks_4 + +- name: different networks, comparisons=allow_more_present + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_2 }}" + networks_cli_compatible: true + comparisons: + network_mode: ignore # otherwise we'd have to set network_mode to nname_1 + networks: allow_more_present + force_kill: true + register: networks_5 + +- name: different networks, comparisons=strict + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_2 }}" + networks_cli_compatible: true + comparisons: + networks: strict + force_kill: true + register: networks_6 + +- name: less networks, comparisons=strict + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: [] + networks_cli_compatible: true + comparisons: + networks: strict + force_kill: true + register: networks_7 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + # networks_1 has networks nname_1 + - networks_1 is changed + - networks_1.container.NetworkSettings.Networks | length == 1 + - nname_1 in networks_1.container.NetworkSettings.Networks + # networks_2 has networks nname_1 + - networks_2 is not changed + - networks_2.container.NetworkSettings.Networks | length == 1 + - nname_1 in networks_2.container.NetworkSettings.Networks + # networks_3 has networks nname_1 + - networks_3 is not changed + - networks_3.container.NetworkSettings.Networks | length == 1 + - nname_1 in networks_3.container.NetworkSettings.Networks + # networks_4 has networks nname_1 + - networks_4 is not changed + - networks_4.container.NetworkSettings.Networks | length == 1 + - nname_1 in networks_4.container.NetworkSettings.Networks + # networks_5 has networks nname_1, nname_2 + - networks_5 is changed + - networks_5.container.NetworkSettings.Networks | length == 2 + - nname_1 in networks_5.container.NetworkSettings.Networks + - nname_2 in networks_5.container.NetworkSettings.Networks + # networks_6 has networks nname_2 + - networks_6 is changed + - networks_6.container.NetworkSettings.Networks | length == 1 + - nname_2 in networks_6.container.NetworkSettings.Networks + # networks_7 has no networks + - networks_7 is changed + - networks_7.container.NetworkSettings.Networks | length == 0 + +#################################################################### +## networks with IP address ######################################## +#################################################################### + +- name: create container (stopped) with one network and fixed IP + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: stopped + networks: + - name: "{{ nname_3 }}" + ipv4_address: "{{ nname_3_ipv4_2 }}" + ipv6_address: "{{ nname_3_ipv6_2 }}" + networks_cli_compatible: true + register: networks_1 + +- name: create container (stopped) with one network and fixed IP (idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: stopped + networks: + - name: "{{ nname_3 }}" + ipv4_address: "{{ nname_3_ipv4_2 }}" + ipv6_address: "{{ nname_3_ipv6_2 }}" + networks_cli_compatible: true + register: networks_2 + +- name: create container (stopped) with one network and fixed IP (different IPv4) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: stopped + networks: + - name: "{{ nname_3 }}" + ipv4_address: "{{ nname_3_ipv4_3 }}" + ipv6_address: "{{ nname_3_ipv6_2 }}" + networks_cli_compatible: true + register: networks_3 + +- name: create container (stopped) with one network and fixed IP (different IPv6) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: stopped + networks: + - name: "{{ nname_3 }}" + ipv4_address: "{{ nname_3_ipv4_3 }}" + ipv6_address: "{{ nname_3_ipv6_3 }}" + networks_cli_compatible: true + register: networks_4 + +- name: create container (started) with one network and fixed IP + docker_container: + name: "{{ cname }}" + state: started + register: networks_5 + +- name: create container (started) with one network and fixed IP (different IPv4) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_3 }}" + ipv4_address: "{{ nname_3_ipv4_4 }}" + ipv6_address: "{{ nname_3_ipv6_3 }}" + networks_cli_compatible: true + force_kill: true + register: networks_6 + +- name: create container (started) with one network and fixed IP (different IPv6) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_3 }}" + ipv4_address: "{{ nname_3_ipv4_4 }}" + ipv6_address: "{{ nname_3_ipv6_4 }}" + networks_cli_compatible: true + force_kill: true + register: networks_7 + +- name: create container (started) with one network and fixed IP (idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + networks: + - name: "{{ nname_3 }}" + ipv4_address: "{{ nname_3_ipv4_4 }}" + ipv6_address: "{{ nname_3_ipv6_4 }}" + networks_cli_compatible: true + register: networks_8 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - networks_1 is changed + - networks_1.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_2 + - networks_1.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_2 | normalize_ipaddr + - networks_1.container.NetworkSettings.Networks[nname_3].IPAddress == "" + - networks_1.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == "" + - networks_2 is not changed + - networks_2.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_2 + - networks_2.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_2 | normalize_ipaddr + - networks_2.container.NetworkSettings.Networks[nname_3].IPAddress == "" + - networks_2.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == "" + - networks_3 is changed + - networks_3.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3 + - networks_3.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_2 | normalize_ipaddr + - networks_3.container.NetworkSettings.Networks[nname_3].IPAddress == "" + - networks_3.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == "" + - networks_4 is changed + - networks_4.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3 + - networks_4.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr + - networks_4.container.NetworkSettings.Networks[nname_3].IPAddress == "" + - networks_4.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == "" + - networks_5 is changed + - networks_5.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3 + - networks_5.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr + - networks_5.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_3 + - networks_5.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr + - networks_6 is changed + - networks_6.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4 + - networks_6.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr + - networks_6.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4 + - networks_6.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr + - networks_7 is changed + - networks_7.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4 + - networks_7.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_4 | normalize_ipaddr + - networks_7.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4 + - networks_7.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | normalize_ipaddr == nname_3_ipv6_4 | normalize_ipaddr + - networks_8 is not changed + - networks_8.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4 + - networks_8.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_4 | normalize_ipaddr + - networks_8.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4 + - networks_8.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | normalize_ipaddr == nname_3_ipv6_4 | normalize_ipaddr + +#################################################################### +#################################################################### +#################################################################### + +- name: Delete networks + docker_network: + name: "{{ network_name }}" + state: absent + force: true + loop: + - "{{ nname_1 }}" + - "{{ nname_2 }}" + - "{{ nname_3 }}" + loop_control: + loop_var: network_name diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/options.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/options.yml new file mode 100644 index 000000000..1254fb52d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/options.yml @@ -0,0 +1,4696 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-options' }}" + cname_h1: "{{ cname_prefix ~ '-options-h1' }}" + cname_h2: "{{ cname_prefix ~ '-options-h2' }}" + cname_h3: "{{ cname_prefix ~ '-options-h3' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname, cname_h1, cname_h2, cname_h3] }}" + +#################################################################### +## auto_remove ##################################################### +#################################################################### + +- name: auto_remove + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "echo"' + name: "{{ cname }}" + state: started + auto_remove: true + register: auto_remove_1 + +- name: Give container 1 second to be sure it terminated + pause: + seconds: 1 + +- name: auto_remove (verify) + docker_container: + name: "{{ cname }}" + state: absent + register: auto_remove_2 + +- assert: + that: + - auto_remove_1 is changed + - auto_remove_2 is not changed + +#################################################################### +## blkio_weight #################################################### +#################################################################### + +- name: blkio_weight + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + blkio_weight: 123 + register: blkio_weight_1 + ignore_errors: true + +- name: blkio_weight (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + blkio_weight: 123 + register: blkio_weight_2 + ignore_errors: true + +- name: blkio_weight (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + blkio_weight: 234 + force_kill: true + register: blkio_weight_3 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- when: blkio_weight_1 is failed + assert: + that: + - "'setting cgroup config for procHooks process caused: failed to write' in blkio_weight_1.msg" + +- when: blkio_weight_1 is not failed + assert: + that: + - blkio_weight_1 is changed + - blkio_weight_2 is not failed + - "blkio_weight_2 is not changed or 'Docker warning: Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.' in (blkio_weight_2.warnings | default([]))" + - blkio_weight_3 is not failed + - blkio_weight_3 is changed + +#################################################################### +## cap_drop, capabilities ########################################## +#################################################################### + +- name: capabilities, cap_drop + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + capabilities: + - sys_time + cap_drop: + - all + register: capabilities_1 + +- name: capabilities, cap_drop (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + capabilities: + - sys_time + cap_drop: + - all + register: capabilities_2 + +- name: capabilities, cap_drop (less) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + capabilities: [] + cap_drop: + - all + register: capabilities_3 + +- name: capabilities, cap_drop (changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + capabilities: + - setgid + cap_drop: + - all + force_kill: true + register: capabilities_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - capabilities_1 is changed + - capabilities_2 is not changed + - capabilities_3 is not changed + - capabilities_4 is changed + +#################################################################### +## cgroupns_mode ################################################### +#################################################################### + +- name: cgroupns_mode + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + cgroupns_mode: host + register: cgroupns_mode_1 + ignore_errors: true + +- name: cgroupns_mode (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + cgroupns_mode: host + register: cgroupns_mode_2 + ignore_errors: true + +- name: cgroupns_mode (changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + cgroupns_mode: private + register: cgroupns_mode_3 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - cgroupns_mode_1 is changed + - cgroupns_mode_2 is not changed and cgroupns_mode_2 is not failed + - >- + cgroupns_mode_3 is changed or + 'Docker warning: Your kernel does not support cgroup namespaces. Cgroup namespace setting discarded.' in (cgroupns_mode_3.warnings | default([])) or + (cgroupns_mode_3 is failed and 'error mounting "cgroup" to rootfs at "/sys/fs/cgroup"' in cgroupns_mode_3.msg) + when: docker_api_version is version('1.41', '>=') and cgroupns_mode_1 is not failed +- assert: + that: + - >- + 'error mounting "cgroup" to rootfs at "/sys/fs/cgroup"' in cgroupns_mode_1.msg + when: docker_api_version is version('1.41', '>=') and cgroupns_mode_1 is failed +- assert: + that: + - cgroupns_mode_1 is failed + - | + ('API version is ' ~ docker_api_version ~ '.') in cgroupns_mode_1.msg and 'Minimum version required is 1.41 ' in cgroupns_mode_1.msg + when: docker_api_version is version('1.41', '<') + +#################################################################### +## cgroup_parent ################################################### +#################################################################### + +- name: cgroup_parent + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + cgroup_parent: '' + register: cgroup_parent_1 + +- name: cgroup_parent (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + cgroup_parent: '' + register: cgroup_parent_2 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - cgroup_parent_1 is changed + - cgroup_parent_2 is not changed + +#################################################################### +## command ######################################################### +#################################################################### + +# old + +- name: command (compatibility) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + command: '/bin/sh -v -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: command_1 + +- name: command (compatibility, idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + command: '/bin/sh -v -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: command_2 + +- name: command (compatibility, idempotency, list) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + command: + - /bin/sh + - '-v' + - '-c' + - '"sleep 10m"' + name: "{{ cname }}" + state: started + register: command_3 + +- name: command (compatibility, fewer parameters) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + force_kill: true + register: command_4 + +- name: command (compatibility, empty list) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + command: [] + name: "{{ cname }}" + state: started + force_kill: true + register: command_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - command_1 is changed + - command_2 is not changed + - command_3 is not changed + - command_4 is changed + - command_5 is not changed + +# new + +- name: command (correct) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + command: '/bin/sh -v -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: command_1 + +- name: command (correct, idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + command: '/bin/sh -v -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: command_2 + +- name: command (correct, idempotency, list) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + command: + - /bin/sh + - '-v' + - '-c' + - sleep 10m + name: "{{ cname }}" + state: started + register: command_3 + +- name: command (correct, fewer parameters) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + force_kill: true + register: command_4 + +- name: command (correct, empty list) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + command: [] + name: "{{ cname }}" + state: started + force_kill: true + register: command_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - command_1 is changed + - command_2 is not changed + - command_3 is not changed + - command_4 is changed + - command_5 is changed + +#################################################################### +## cpu_period ###################################################### +#################################################################### + +- name: cpu_period + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_period: 90000 + state: started + register: cpu_period_1 + +- name: cpu_period (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_period: 90000 + state: started + register: cpu_period_2 + +- name: cpu_period (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_period: 50000 + state: started + force_kill: true + register: cpu_period_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - cpu_period_1 is changed + - cpu_period_2 is not changed + - cpu_period_3 is changed + +#################################################################### +## cpu_quota ####################################################### +#################################################################### + +- name: cpu_quota + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_quota: 150000 + state: started + register: cpu_quota_1 + +- name: cpu_quota (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_quota: 150000 + state: started + register: cpu_quota_2 + +- name: cpu_quota (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_quota: 50000 + state: started + force_kill: true + register: cpu_quota_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - cpu_quota_1 is changed + - cpu_quota_2 is not changed + - cpu_quota_3 is changed + +#################################################################### +## cpu_shares ###################################################### +#################################################################### + +- name: cpu_shares + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_shares: 900 + state: started + register: cpu_shares_1 + +- name: cpu_shares (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_shares: 900 + state: started + register: cpu_shares_2 + +- name: cpu_shares (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpu_shares: 1100 + state: started + force_kill: true + register: cpu_shares_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - cpu_shares_1 is changed + - cpu_shares_2 is not changed + - cpu_shares_3 is changed + +#################################################################### +## cpuset_cpus ##################################################### +#################################################################### + +- name: cpuset_cpus + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpuset_cpus: "0" + state: started + register: cpuset_cpus_1 + +- name: cpuset_cpus (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpuset_cpus: "0" + state: started + register: cpuset_cpus_2 + +- name: cpuset_cpus (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpuset_cpus: "1" + state: started + force_kill: true + # This will fail if the system the test is run on doesn't have + # multiple CPUs/cores available. + ignore_errors: true + register: cpuset_cpus_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - cpuset_cpus_1 is changed + - cpuset_cpus_2 is not changed + - cpuset_cpus_3 is failed or cpuset_cpus_3 is changed + +#################################################################### +## cpuset_mems ##################################################### +#################################################################### + +- name: cpuset_mems + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpuset_mems: "0" + state: started + register: cpuset_mems_1 + +- name: cpuset_mems (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpuset_mems: "0" + state: started + register: cpuset_mems_2 + +- name: cpuset_mems (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpuset_mems: "1" + state: started + force_kill: true + # This will fail if the system the test is run on doesn't have + # multiple MEMs available. + ignore_errors: true + register: cpuset_mems_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - cpuset_mems_1 is changed + - cpuset_mems_2 is not changed + - cpuset_mems_3 is failed or cpuset_mems_3 is changed + +#################################################################### +## cpus ############################################################ +#################################################################### + +- name: cpus + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpus: 1 + state: started + register: cpus_1 + +- name: cpus (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpus: 1 + state: started + register: cpus_2 + +- name: cpus (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpus: 1.5 + state: started + force_kill: true + # This will fail if the system the test is run on doesn't have + # multiple MEMs available. + register: cpus_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - cpus_1 is changed + - cpus_2 is not changed and cpus_2 is not failed + - cpus_3 is failed or cpus_3 is changed + +#################################################################### +## debug ########################################################### +#################################################################### + +- name: debug (create) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: present + debug: true + register: debug_1 + +- name: debug (start) + docker_container: + name: "{{ cname }}" + state: started + debug: true + register: debug_2 + +- name: debug (stop) + docker_container: + image: "{{ docker_test_image_alpine }}" + name: "{{ cname }}" + state: stopped + force_kill: true + debug: true + register: debug_3 + +- name: debug (absent) + docker_container: + name: "{{ cname }}" + state: absent + debug: true + force_kill: true + register: debug_4 + +- assert: + that: + - debug_1 is changed + - debug_2 is changed + - debug_3 is changed + - debug_4 is changed + +#################################################################### +## detach, cleanup ################################################# +#################################################################### + +- name: detach without cleanup + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_hello_world }}" + detach: false + register: detach_no_cleanup + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + register: detach_no_cleanup_cleanup + diff: false + +- name: detach with cleanup + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_hello_world }}" + detach: false + cleanup: true + register: detach_cleanup + +- name: cleanup (unnecessary) + docker_container: + name: "{{ cname }}" + state: absent + register: detach_cleanup_cleanup + diff: false + +- name: detach with auto_remove and cleanup + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_hello_world }}" + detach: false + auto_remove: true + cleanup: true + register: detach_auto_remove + ignore_errors: true + +- name: cleanup (unnecessary) + docker_container: + name: "{{ cname }}" + state: absent + register: detach_auto_remove_cleanup + diff: false + +- name: detach with cleanup and non-zero status + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "exit 42"' + detach: false + cleanup: true + register: detach_cleanup_nonzero + ignore_errors: true + +- assert: + that: + # NOTE that 'Output' sometimes fails to contain the correct output + # of hello-world. We don't know why this happens, but it happens + # often enough to be annoying. That's why we disable this for now, + # and simply test that 'Output' is contained in the result. + - "'Output' in detach_no_cleanup.container" + - detach_no_cleanup.status == 0 + # - "'Hello from Docker!' in detach_no_cleanup.container.Output" + - detach_no_cleanup_cleanup is changed + - "'Output' in detach_cleanup.container" + - detach_cleanup.status == 0 + # - "'Hello from Docker!' in detach_cleanup.container.Output" + - detach_cleanup_cleanup is not changed + - detach_cleanup_nonzero is failed + - detach_cleanup_nonzero.status == 42 + - "'Output' in detach_cleanup_nonzero.container" + - "detach_cleanup_nonzero.container.Output == ''" + - "'Cannot retrieve result as auto_remove is enabled' == detach_auto_remove.container.Output" + - detach_auto_remove_cleanup is not changed + +#################################################################### +## devices ######################################################### +#################################################################### + +- name: devices + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/random:/dev/virt-random:rwm" + - "/dev/urandom:/dev/virt-urandom:rwm" + register: devices_1 + +- name: devices (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/urandom:/dev/virt-urandom:rwm" + - "/dev/random:/dev/virt-random:rwm" + register: devices_2 + +- name: devices (less) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/random:/dev/virt-random:rwm" + register: devices_3 + +- name: devices (changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + devices: + - "/dev/random:/dev/virt-random:rwm" + - "/dev/null:/dev/virt-null:rwm" + force_kill: true + register: devices_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - devices_1 is changed + - devices_2 is not changed + - devices_3 is not changed + - devices_4 is changed + +#################################################################### +## device_read_bps ################################################# +#################################################################### + +- name: device_read_bps + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_read_bps: + - path: /dev/random + rate: 20M + - path: /dev/urandom + rate: 10K + register: device_read_bps_1 + ignore_errors: true + +- name: device_read_bps (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_read_bps: + - path: /dev/urandom + rate: 10K + - path: /dev/random + rate: 20M + register: device_read_bps_2 + ignore_errors: true + +- name: device_read_bps (lesser entries) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_read_bps: + - path: /dev/random + rate: 20M + register: device_read_bps_3 + ignore_errors: true + +- name: device_read_bps (changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_read_bps: + - path: /dev/random + rate: 10M + - path: /dev/urandom + rate: 5K + force_kill: true + register: device_read_bps_4 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- when: device_read_bps_1 is not failed + assert: + that: + - device_read_bps_1 is not failed + - device_read_bps_1 is changed + - device_read_bps_2 is not failed + - device_read_bps_2 is not changed + - device_read_bps_3 is not failed + - device_read_bps_3 is not changed + - device_read_bps_4 is not failed + - device_read_bps_4 is changed + +- when: device_read_bps_1 is failed + assert: + that: + - "'error setting cgroup config for procHooks process' in device_read_bps_1.msg and 'blkio.throttle.read_bps_device: no such device' in device_read_bps_1.msg" + +#################################################################### +## device_read_iops ################################################ +#################################################################### + +- name: device_read_iops + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_read_iops: + - path: /dev/random + rate: 10 + - path: /dev/urandom + rate: 20 + register: device_read_iops_1 + ignore_errors: true + +- name: device_read_iops (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_read_iops: + - path: /dev/urandom + rate: "20" + - path: /dev/random + rate: 10 + register: device_read_iops_2 + ignore_errors: true + +- name: device_read_iops (less) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_read_iops: + - path: /dev/random + rate: 10 + register: device_read_iops_3 + ignore_errors: true + +- name: device_read_iops (changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_read_iops: + - path: /dev/random + rate: 30 + - path: /dev/urandom + rate: 50 + force_kill: true + register: device_read_iops_4 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- when: device_read_iops_1 is not failed + assert: + that: + - device_read_iops_1 is not failed + - device_read_iops_1 is changed + - device_read_iops_2 is not failed + - device_read_iops_2 is not changed + - device_read_iops_3 is not failed + - device_read_iops_3 is not changed + - device_read_iops_4 is not failed + - device_read_iops_4 is changed + +- when: device_read_iops_1 is failed + assert: + that: + - "'error setting cgroup config for procHooks process' in device_read_iops_1.msg and 'blkio.throttle.read_iops_device: no such device' in device_read_iops_1.msg" + +#################################################################### +## device_write_bps and device_write_iops ########################## +#################################################################### + +- name: device_write_bps and device_write_iops + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_write_bps: + - path: /dev/random + rate: 10M + device_write_iops: + - path: /dev/urandom + rate: 30 + register: device_write_limit_1 + ignore_errors: true + +- name: device_write_bps and device_write_iops (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_write_bps: + - path: /dev/random + rate: 10M + device_write_iops: + - path: /dev/urandom + rate: 30 + register: device_write_limit_2 + ignore_errors: true + +- name: device_write_bps device_write_iops (changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_write_bps: + - path: /dev/random + rate: 20K + device_write_iops: + - path: /dev/urandom + rate: 100 + force_kill: true + register: device_write_limit_3 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- when: device_write_limit_1 is not failed + assert: + that: + - device_write_limit_1 is not failed and device_write_limit_2 is not failed and device_write_limit_3 is not failed + - device_write_limit_1 is changed + - device_write_limit_2 is not changed + - device_write_limit_3 is changed + +- when: device_write_limit_1 is failed + assert: + that: + - "'error setting cgroup config for procHooks process' in device_write_limit_1.msg and 'blkio.throttle.write_bps_device: no such device' in device_write_limit_1.msg" + +#################################################################### +## device_requests ################################################# +#################################################################### + +- name: device_requests + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_requests: [] + register: device_requests_1 + ignore_errors: true + +- name: device_requests (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + device_requests: [] + register: device_requests_2 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - device_requests_1 is changed + - device_requests_2 is not changed + when: docker_api_version is version('1.40', '>=') +- assert: + that: + - device_requests_1 is failed + - | + ('API version is ' ~ docker_api_version ~ '.') in device_requests_1.msg and 'Minimum version required is 1.40 ' in device_requests_1.msg + when: docker_api_version is version('1.40', '<') + +#################################################################### +## dns_opts ######################################################## +#################################################################### + +- name: dns_opts + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_opts: + - "timeout:10" + - rotate + register: dns_opts_1 + +- name: dns_opts (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_opts: + - rotate + - "timeout:10" + register: dns_opts_2 + +- name: dns_opts (less resolv.conf options) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_opts: + - "timeout:10" + register: dns_opts_3 + +- name: dns_opts (more resolv.conf options) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_opts: + - "timeout:10" + - no-check-names + force_kill: true + register: dns_opts_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - dns_opts_1 is changed + - dns_opts_2 is not changed + - dns_opts_3 is not changed + - dns_opts_4 is changed + +#################################################################### +## dns_search_domains ############################################## +#################################################################### + +- name: dns_search_domains + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_search_domains: + - example.com + - example.org + register: dns_search_domains_1 + +- name: dns_search_domains (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_search_domains: + - example.com + - example.org + register: dns_search_domains_2 + +- name: dns_search_domains (different order) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_search_domains: + - example.org + - example.com + force_kill: true + register: dns_search_domains_3 + +- name: dns_search_domains (changed elements) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_search_domains: + - ansible.com + - example.com + force_kill: true + register: dns_search_domains_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - dns_search_domains_1 is changed + - dns_search_domains_2 is not changed + - dns_search_domains_3 is changed + - dns_search_domains_4 is changed + +#################################################################### +## dns_servers ##################################################### +#################################################################### + +- name: dns_servers + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_servers: + - 1.1.1.1 + - 8.8.8.8 + register: dns_servers_1 + +- name: dns_servers (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_servers: + - 1.1.1.1 + - 8.8.8.8 + register: dns_servers_2 + +- name: dns_servers (changed order) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_servers: + - 8.8.8.8 + - 1.1.1.1 + force_kill: true + register: dns_servers_3 + +- name: dns_servers (changed elements) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + dns_servers: + - 8.8.8.8 + - 9.9.9.9 + force_kill: true + register: dns_servers_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - dns_servers_1 is changed + - dns_servers_2 is not changed + - dns_servers_3 is changed + - dns_servers_4 is changed + +#################################################################### +## domainname ###################################################### +#################################################################### + +- name: domainname + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + domainname: example.com + state: started + register: domainname_1 + +- name: domainname (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + domainname: example.com + state: started + register: domainname_2 + +- name: domainname (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + domainname: example.org + state: started + force_kill: true + register: domainname_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - domainname_1 is changed + - domainname_2 is not changed + - domainname_3 is changed + +#################################################################### +## entrypoint ###################################################### +#################################################################### + +# Old + +- name: entrypoint (compatibility) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + entrypoint: + - /bin/sh + - "-v" + - "-c" + - "'sleep 10m'" + name: "{{ cname }}" + state: started + register: entrypoint_1 + +- name: entrypoint (compatibility, idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + entrypoint: + - /bin/sh + - "-v" + - "-c" + - "'sleep 10m'" + name: "{{ cname }}" + state: started + register: entrypoint_2 + +- name: entrypoint (compatibility, change order, should not be idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + entrypoint: + - /bin/sh + - "-c" + - "'sleep 10m'" + - "-v" + name: "{{ cname }}" + state: started + force_kill: true + register: entrypoint_3 + +- name: entrypoint (compatibility, fewer parameters) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + entrypoint: + - /bin/sh + - "-c" + - "'sleep 10m'" + name: "{{ cname }}" + state: started + force_kill: true + register: entrypoint_4 + +- name: entrypoint (compatibility, other parameters) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + entrypoint: + - /bin/sh + - "-c" + - "'sleep 5m'" + name: "{{ cname }}" + state: started + force_kill: true + register: entrypoint_5 + +- name: entrypoint (compatibility, force empty) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: compatibility + entrypoint: [] + name: "{{ cname }}" + state: started + force_kill: true + register: entrypoint_6 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - entrypoint_1 is changed + - entrypoint_2 is not changed + - entrypoint_3 is changed + - entrypoint_4 is changed + - entrypoint_5 is changed + - entrypoint_6 is not changed + +# New + +- name: entrypoint (correct) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + entrypoint: + - /bin/sh + - "-v" + - "-c" + - "sleep 10m" + name: "{{ cname }}" + state: started + register: entrypoint_1 + +- name: entrypoint (correct, idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + entrypoint: + - /bin/sh + - "-v" + - "-c" + - "sleep 10m" + name: "{{ cname }}" + state: started + register: entrypoint_2 + +- name: entrypoint (correct, change order, should not be idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + entrypoint: + - /bin/sh + - "-c" + - "sleep 10m" + - "-v" + name: "{{ cname }}" + state: started + force_kill: true + register: entrypoint_3 + +- name: entrypoint (correct, fewer parameters) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + entrypoint: + - /bin/sh + - "-c" + - "sleep 10m" + name: "{{ cname }}" + state: started + force_kill: true + register: entrypoint_4 + +- name: entrypoint (correct, other parameters) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + entrypoint: + - /bin/sh + - "-c" + - "sleep 5m" + name: "{{ cname }}" + state: started + force_kill: true + register: entrypoint_5 + +- name: entrypoint (correct, force empty) + docker_container: + image: "{{ docker_test_image_alpine }}" + command_handling: correct + entrypoint: [] + name: "{{ cname }}" + state: started + force_kill: true + register: entrypoint_6 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - entrypoint_1 is changed + - entrypoint_2 is not changed + - entrypoint_3 is changed + - entrypoint_4 is changed + - entrypoint_5 is changed + - entrypoint_6 is changed + +#################################################################### +## env ############################################################# +#################################################################### + +- name: env + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env: + TEST1: val1 + TEST2: val2 + TEST3: "False" + TEST4: "true" + TEST5: "yes" + register: env_1 + +- name: env (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env: + TEST2: val2 + TEST1: val1 + TEST5: "yes" + TEST3: "False" + TEST4: "true" + register: env_2 + +- name: env (less environment variables) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env: + TEST1: val1 + register: env_3 + +- name: env (more environment variables) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env: + TEST1: val1 + TEST3: val3 + force_kill: true + register: env_4 + +- name: env (fail unwrapped values) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env: + TEST1: true + force_kill: true + register: env_5 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - env_1 is changed + - "'TEST1=val1' in env_1.container.Config.Env" + - "'TEST2=val2' in env_1.container.Config.Env" + - "'TEST3=False' in env_1.container.Config.Env" + - "'TEST4=true' in env_1.container.Config.Env" + - "'TEST5=yes' in env_1.container.Config.Env" + - env_2 is not changed + - env_3 is not changed + - "'TEST1=val1' in env_4.container.Config.Env" + - "'TEST2=val2' not in env_4.container.Config.Env" + - "'TEST3=val3' in env_4.container.Config.Env" + - env_4 is changed + - env_5 is failed + - "('Non-string value found for env option.') in env_5.msg" + +#################################################################### +## env_file ######################################################### +#################################################################### + +- name: Copy env-file + copy: + src: env-file + dest: "{{ remote_tmp_dir }}/env-file" + +- name: env_file + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env_file: "{{ remote_tmp_dir }}/env-file" + register: env_file_1 + +- name: env_file (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env_file: "{{ remote_tmp_dir }}/env-file" + register: env_file_2 + +- name: env_file (with env, idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env_file: "{{ remote_tmp_dir }}/env-file" + env: + TEST3: val3 + register: env_file_3 + +- name: env_file (with env) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env_file: "{{ remote_tmp_dir }}/env-file" + env: + TEST1: val1 + TEST3: val3 + force_kill: true + register: env_file_4 + +- name: env_file (with env, idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env_file: "{{ remote_tmp_dir }}/env-file" + env: + TEST1: val1 + register: env_file_5 + +- name: env_file (with env, override) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + env_file: "{{ remote_tmp_dir }}/env-file" + env: + TEST2: val2 + TEST4: val4alt + force_kill: true + register: env_file_6 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - env_file_1 is changed + - "'TEST3=val3' in env_file_1.container.Config.Env" + - "'TEST4=val4' in env_file_1.container.Config.Env" + - env_file_2 is not changed + - env_file_3 is not changed + - env_file_4 is changed + - "'TEST1=val1' in env_file_4.container.Config.Env" + - "'TEST3=val3' in env_file_4.container.Config.Env" + - "'TEST4=val4' in env_file_4.container.Config.Env" + - env_file_5 is not changed + - env_file_6 is changed + - "'TEST2=val2' in env_file_6.container.Config.Env" + - "'TEST3=val3' in env_file_6.container.Config.Env" + - "'TEST4=val4alt' in env_file_6.container.Config.Env" + +#################################################################### +## etc_hosts ####################################################### +#################################################################### + +- name: etc_hosts + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + etc_hosts: + example.com: 1.2.3.4 + example.org: 4.3.2.1 + register: etc_hosts_1 + +- name: etc_hosts (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + etc_hosts: + example.org: 4.3.2.1 + example.com: 1.2.3.4 + register: etc_hosts_2 + +- name: etc_hosts (less hosts) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + etc_hosts: + example.com: 1.2.3.4 + register: etc_hosts_3 + +- name: etc_hosts (more hosts) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + etc_hosts: + example.com: 1.2.3.4 + example.us: 1.2.3.5 + force_kill: true + register: etc_hosts_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - etc_hosts_1 is changed + - etc_hosts_2 is not changed + - etc_hosts_3 is not changed + - etc_hosts_4 is changed + +#################################################################### +## exposed_ports ################################################### +#################################################################### + +- name: exposed_ports + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + exposed_ports: + - "9001" + - "9002" + register: exposed_ports_1 + +- name: exposed_ports (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + exposed_ports: + - "9002" + - "9001" + register: exposed_ports_2 + +- name: exposed_ports (less ports) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + exposed_ports: + - "9002" + register: exposed_ports_3 + +- name: exposed_ports (more ports) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + exposed_ports: + - "9002" + - "9003" + force_kill: true + register: exposed_ports_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - exposed_ports_1 is changed + - exposed_ports_2 is not changed + - exposed_ports_3 is not changed + - exposed_ports_4 is changed + +#################################################################### +## force_kill ###################################################### +#################################################################### + +# TODO: - force_kill + +#################################################################### +## groups ########################################################## +#################################################################### + +- name: groups + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "1234" + - "5678" + register: groups_1 + +- name: groups (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "5678" + - "1234" + register: groups_2 + +- name: groups (less groups) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "1234" + register: groups_3 + +- name: groups (more groups) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + groups: + - "1234" + - "2345" + force_kill: true + register: groups_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - groups_1 is changed + - groups_2 is not changed + - groups_3 is not changed + - groups_4 is changed + +#################################################################### +## healthcheck ##################################################### +#################################################################### + +- name: healthcheck + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + healthcheck: + test: + - CMD + - sleep + - 1 + timeout: 2s + interval: 0h0m2s3ms4us + retries: 2 + force_kill: true + register: healthcheck_1 + +- name: healthcheck (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + healthcheck: + test: + - CMD + - sleep + - 1 + timeout: 2s + interval: 0h0m2s3ms4us + retries: 2 + force_kill: true + register: healthcheck_2 + +- name: healthcheck (changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + healthcheck: + test: + - CMD + - sleep + - 1 + timeout: 3s + interval: 0h1m2s3ms4us + retries: 3 + force_kill: true + register: healthcheck_3 + +- name: healthcheck (no change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + force_kill: true + register: healthcheck_4 + +- name: healthcheck (disabled) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + healthcheck: + test: + - NONE + force_kill: true + register: healthcheck_5 + +- name: healthcheck (disabled, idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + healthcheck: + test: + - NONE + force_kill: true + register: healthcheck_6 + +- name: healthcheck (disabled, idempotency, strict) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + healthcheck: + test: + - NONE + force_kill: true + comparisons: + '*': strict + register: healthcheck_7 + +- name: healthcheck (string in healthcheck test, changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + healthcheck: + test: "sleep 1" + force_kill: true + register: healthcheck_8 + +- name: healthcheck (string in healthcheck test, idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + healthcheck: + test: "sleep 1" + force_kill: true + register: healthcheck_9 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - healthcheck_1 is changed + - healthcheck_2 is not changed + - healthcheck_3 is changed + - healthcheck_4 is not changed + - healthcheck_5 is changed + - healthcheck_6 is not changed + - healthcheck_7 is not changed + - healthcheck_8 is changed + - healthcheck_9 is not changed + +#################################################################### +## hostname ######################################################## +#################################################################### + +- name: hostname + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + hostname: me.example.com + state: started + register: hostname_1 + +- name: hostname (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + hostname: me.example.com + state: started + register: hostname_2 + +- name: hostname (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + hostname: me.example.org + state: started + force_kill: true + register: hostname_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - hostname_1 is changed + - hostname_2 is not changed + - hostname_3 is changed + +#################################################################### +## init ############################################################ +#################################################################### + +- name: init + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + init: true + state: started + register: init_1 + +- name: init (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + init: true + state: started + register: init_2 + +- name: init (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + init: false + state: started + force_kill: true + register: init_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - init_1 is changed + - init_2 is not changed + - init_3 is changed + +#################################################################### +## interactive ##################################################### +#################################################################### + +- name: interactive + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + interactive: true + state: started + register: interactive_1 + +- name: interactive (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + interactive: true + state: started + register: interactive_2 + +- name: interactive (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + interactive: false + state: started + force_kill: true + register: interactive_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - interactive_1 is changed + - interactive_2 is not changed + - interactive_3 is changed + +#################################################################### +## image / image_comparison / ignore_image ######################### +#################################################################### + +- name: Pull images to make sure ignore_image test succeeds + # If the image isn't there, it will pull it and return 'changed'. + docker_image: + name: "{{ item }}" + source: pull + loop: + - "{{ docker_test_image_hello_world }}" + - "{{ docker_test_image_registry_nginx }}" + +- name: image + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: image_1 + +- name: image (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: image_2 + diff: true + +- name: ignore_image + docker_container: + image: "{{ docker_test_image_hello_world }}" + comparisons: + image: ignore + name: "{{ cname }}" + state: started + register: ignore_image + diff: true + +- name: ignore_image (labels and env differ in image, image_comparison=current-image) + docker_container: + image: "{{ docker_test_image_registry_nginx }}" + comparisons: + image: ignore + image_comparison: current-image + name: "{{ cname }}" + state: started + register: ignore_image_2 + diff: true + +- name: ignore_image (labels and env differ in image, image_comparison=desired-image) + docker_container: + image: "{{ docker_test_image_registry_nginx }}" + comparisons: + image: ignore + image_comparison: desired-image + name: "{{ cname }}" + state: started + force_kill: true + register: ignore_image_3 + diff: true + +- name: image change + docker_container: + image: "{{ docker_test_image_hello_world }}" + name: "{{ cname }}" + state: started + force_kill: true + register: image_change + diff: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - image_1 is changed + - image_2 is not changed + - ignore_image is not changed + - ignore_image_2 is not changed + - ignore_image_3 is changed + - image_change is changed + +#################################################################### +## image_label_mismatch ############################################ +#################################################################### + +- name: Registering image name + set_fact: + iname_labels: "{{ cname_prefix ~ '-labels' }}" +- name: Registering image name + set_fact: + inames: "{{ inames + [iname_labels] }}" +- name: build image with labels + command: + cmd: "docker build --label img_label=base --tag {{ iname_labels }} -" + stdin: "FROM {{ docker_test_image_alpine }}" + +- name: image_label_mismatch + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: image_label_mismatch_1 + +- name: image_label_mismatch (ignore,unmanaged labels) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: ignore + state: started + register: image_label_mismatch_2 + +- name: image_label_mismatch (ignore,missing img label) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: ignore + labels: {} + state: started + register: image_label_mismatch_3 + +- name: image_label_mismatch (ignore,match img label) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: ignore + labels: + img_label: base + state: started + register: image_label_mismatch_4 + +- name: image_label_mismatch (ignore,mismatched img label) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: ignore + labels: + img_label: override + state: started + force_kill: true + register: image_label_mismatch_5 + +- name: image_label_mismatch (ignore,remove img label) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: ignore + labels: {} + state: started + force_kill: true + register: image_label_mismatch_6 + +- name: image_label_mismatch (fail,unmanaged labels) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: fail + state: started + register: image_label_mismatch_7 + +- name: image_label_mismatch (fail,non-strict,missing img label) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: fail + labels: {} + state: started + register: image_label_mismatch_8 + +- name: image_label_mismatch (fail,strict,missing img label) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: fail + comparisons: + labels: strict + labels: {} + state: started + ignore_errors: true + register: image_label_mismatch_9 + +- name: image_label_mismatch (fail,match img label) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: fail + labels: + img_label: base + state: started + register: image_label_mismatch_10 + +- name: image_label_mismatch (fail,mismatched img label) + docker_container: + image: "{{ iname_labels }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_label_mismatch: fail + labels: + img_label: override + state: started + force_kill: true + register: image_label_mismatch_11 + +- name: cleanup container + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- name: cleanup image + docker_image: + name: "{{ iname_labels }}" + state: absent + diff: false + +- assert: + that: + - image_label_mismatch_1 is changed + - image_label_mismatch_1.container.Config.Labels.img_label == "base" + - image_label_mismatch_2 is not changed + - image_label_mismatch_3 is not changed + - image_label_mismatch_4 is not changed + - image_label_mismatch_5 is changed + - image_label_mismatch_5.container.Config.Labels.img_label == "override" + - image_label_mismatch_6 is changed + - image_label_mismatch_6.container.Config.Labels.img_label == "base" + - image_label_mismatch_7 is not changed + - image_label_mismatch_8 is not changed + - image_label_mismatch_9 is failed + - >- + image_label_mismatch_9.msg == ("Some labels should be removed but are present in the base image. You can set image_label_mismatch to 'ignore' to ignore this error. " ~ 'Labels: "img_label"') + - image_label_mismatch_10 is not changed + - image_label_mismatch_11 is changed + +#################################################################### +## image_name_mismatch ############################################# +#################################################################### + +- name: Pull images to make sure ignore_image test succeeds + # If the image isn't there, it will pull it and return 'changed'. + docker_image: + name: "{{ item }}" + source: pull + loop: + - "{{ docker_test_image_hello_world }}" + - "{{ docker_test_image_registry_nginx }}" + +- name: image + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: image_1 + +- name: image (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: image_2 + diff: true + +- name: ignore_image + docker_container: + image: "{{ docker_test_image_hello_world }}" + ignore_image: true + name: "{{ cname }}" + state: started + register: ignore_image + diff: true + +- name: ignore_image (labels and env differ in image, image_comparison=current-image) + docker_container: + image: "{{ docker_test_image_registry_nginx }}" + ignore_image: true + image_comparison: current-image + name: "{{ cname }}" + state: started + register: ignore_image_2 + diff: true + +- name: ignore_image (labels and env differ in image, image_comparison=desired-image) + docker_container: + image: "{{ docker_test_image_registry_nginx }}" + ignore_image: true + image_comparison: desired-image + name: "{{ cname }}" + state: started + force_kill: true + register: ignore_image_3 + diff: true + +- name: image change + docker_container: + image: "{{ docker_test_image_hello_world }}" + name: "{{ cname }}" + state: started + force_kill: true + register: image_change + diff: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - image_1 is changed + - image_2 is not changed + - ignore_image is not changed + - ignore_image_2 is not changed + - ignore_image_3 is changed + - image_change is changed + +#################################################################### +## image_name_mismatch ############################################# +#################################################################### + +- name: Registering image name + set_fact: + iname_name_mismatch: "{{ cname_prefix ~ '-image-name' }}" +- name: Registering image name + set_fact: + inames: "{{ inames + [iname_name_mismatch] }}" + +- name: Tag hello world image (pulled earlier) with new name + docker_image: + name: "{{ docker_test_image_registry_nginx }}" + source: local + repository: "{{ iname_name_mismatch }}:latest" + +- name: image_name_mismatch + docker_container: + image: "{{ docker_test_image_registry_nginx }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + register: image_name_mismatch_1 + +- name: image_name_mismatch (ignore) + docker_container: + image: "{{ iname_name_mismatch }}:latest" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_name_mismatch: ignore + state: started + register: image_name_mismatch_2 + +- name: image_name_mismatch (recreate) + docker_container: + image: "{{ iname_name_mismatch }}:latest" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + image_name_mismatch: recreate + state: started + force_kill: true + register: image_name_mismatch_3 + +- name: Cleanup container + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- name: Cleanup image + docker_image: + name: "{{ iname_name_mismatch }}" + state: absent + diff: false + +- assert: + that: + - image_name_mismatch_1 is changed + - image_name_mismatch_2 is not changed + - image_name_mismatch_3 is changed + - image_name_mismatch_3.container.Image == image_name_mismatch_2.container.Image + +#################################################################### +## ipc_mode ######################################################## +#################################################################### + +- name: start helpers + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ container_name }}" + state: started + ipc_mode: shareable + loop: + - "{{ cname_h1 }}" + loop_control: + loop_var: container_name + +- name: ipc_mode + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + ipc_mode: "container:{{ cname_h1 }}" + # ipc_mode: shareable + register: ipc_mode_1 + +- name: ipc_mode (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + ipc_mode: "container:{{ cname_h1 }}" + # ipc_mode: shareable + register: ipc_mode_2 + +- name: ipc_mode (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + ipc_mode: private + force_kill: true + register: ipc_mode_3 + +- name: cleanup + docker_container: + name: "{{ container_name }}" + state: absent + force_kill: true + loop: + - "{{ cname }}" + - "{{ cname_h1 }}" + loop_control: + loop_var: container_name + diff: false + +- assert: + that: + - ipc_mode_1 is changed + - ipc_mode_2 is not changed + - ipc_mode_3 is changed + +#################################################################### +## kernel_memory ################################################### +#################################################################### + +- name: kernel_memory + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + kernel_memory: 8M + state: started + register: kernel_memory_1 + ignore_errors: true + +- name: kernel_memory (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + kernel_memory: 8M + state: started + register: kernel_memory_2 + ignore_errors: true + +- name: kernel_memory (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + kernel_memory: 6M + state: started + force_kill: true + register: kernel_memory_3 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - kernel_memory_1 is changed + - kernel_memory_2 is not changed + - kernel_memory_3 is changed + when: + - kernel_memory_1 is not failed or 'kernel memory accounting disabled in this runc build' not in kernel_memory_1.msg + - "'Docker warning: Specifying a kernel memory limit is deprecated and will be removed in a future release.' not in (kernel_memory_1.warnings | default([]))" + # API version 1.42 seems to remove the kernel memory option completely + - "'KernelMemory' in kernel_memory_1.container.HostConfig or docker_api_version is version('1.42', '<')" + +#################################################################### +## kill_signal ##################################################### +#################################################################### + +# TODO: - kill_signal + +#################################################################### +## labels ########################################################## +#################################################################### + +- name: labels + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.1: hello + ansible.test.2: world + register: labels_1 + +- name: labels (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.2: world + ansible.test.1: hello + register: labels_2 + +- name: labels (less labels) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.1: hello + register: labels_3 + +- name: labels (more labels) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + labels: + ansible.test.1: hello + ansible.test.3: ansible + force_kill: true + register: labels_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - labels_1 is changed + - labels_2 is not changed + - labels_3 is not changed + - labels_4 is changed + +#################################################################### +## links ########################################################### +#################################################################### + +- name: start helpers + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ container_name }}" + state: started + loop: + - "{{ cname_h1 }}" + - "{{ cname_h2 }}" + - "{{ cname_h3 }}" + loop_control: + loop_var: container_name + +- name: links + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + links: + - "{{ cname_h1 }}:test1" + - "{{ cname_h2 }}:test2" + register: links_1 + +- name: links (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + links: + - "{{ cname_h2 }}:test2" + - "{{ cname_h1 }}:test1" + register: links_2 + +- name: links (less links) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + links: + - "{{ cname_h1 }}:test1" + register: links_3 + +- name: links (more links) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + links: + - "{{ cname_h1 }}:test1" + - "{{ cname_h3 }}:test3" + force_kill: true + register: links_4 + +- name: cleanup + docker_container: + name: "{{ container_name }}" + state: absent + force_kill: true + loop: + - "{{ cname }}" + - "{{ cname_h1 }}" + - "{{ cname_h2 }}" + - "{{ cname_h3 }}" + loop_control: + loop_var: container_name + diff: false + +- assert: + that: + - links_1 is changed + - links_2 is not changed + - links_3 is not changed + - links_4 is changed + +#################################################################### +## log_driver ###################################################### +#################################################################### + +- name: log_driver + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + log_driver: json-file + register: log_driver_1 + +- name: log_driver (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + log_driver: json-file + register: log_driver_2 + +- name: log_driver (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + log_driver: syslog + force_kill: true + register: log_driver_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - log_driver_1 is changed + - log_driver_2 is not changed + - log_driver_3 is changed + +#################################################################### +## log_options ##################################################### +#################################################################### + +- name: log_options + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + log_driver: json-file + log_options: + labels: production_status + env: os,customer + max-file: 5 + register: log_options_1 + +- name: log_options (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + log_driver: json-file + log_options: + env: os,customer + labels: production_status + max-file: 5 + register: log_options_2 + +- name: log_options (less log options) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + log_driver: json-file + log_options: + labels: production_status + register: log_options_3 + +- name: log_options (more log options) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + log_driver: json-file + log_options: + labels: production_status + max-size: 10m + force_kill: true + register: log_options_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - log_options_1 is changed + - log_options_2 is not changed + - "'Non-string value found for log_options option \\'max-file\\'. The value is automatically converted to \\'5\\'. If this is not correct, or you want to +avoid such warnings, please quote the value.' in (log_options_2.warnings | default([]))" + - log_options_3 is not changed + - log_options_4 is changed + +#################################################################### +## mac_address ##################################################### +#################################################################### + +- name: mac_address + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + mac_address: 92:d0:c6:0a:29:33 + state: started + register: mac_address_1 + +- name: mac_address (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + mac_address: 92:d0:c6:0a:29:33 + state: started + register: mac_address_2 + +- name: mac_address (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + mac_address: 92:d0:c6:0a:29:44 + state: started + force_kill: true + register: mac_address_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - mac_address_1 is changed + - mac_address_2 is not changed + - mac_address_3 is changed + +#################################################################### +## memory ########################################################## +#################################################################### + +- name: memory + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory: 64M + state: started + register: memory_1 + +- name: memory (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory: 64M + state: started + register: memory_2 + +- name: memory (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory: 48M + state: started + force_kill: true + register: memory_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - memory_1 is changed + - memory_2 is not changed + - memory_3 is changed + +#################################################################### +## memory_reservation ############################################## +#################################################################### + +- name: memory_reservation + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory_reservation: 64M + state: started + register: memory_reservation_1 + +- name: memory_reservation (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory_reservation: 64M + state: started + register: memory_reservation_2 + +- name: memory_reservation (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory_reservation: 48M + state: started + force_kill: true + register: memory_reservation_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - memory_reservation_1 is changed + - memory_reservation_2 is not changed + - memory_reservation_3 is changed + +#################################################################### +## memory_swap ##################################################### +#################################################################### + +- name: memory_swap + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + # Docker daemon does not accept memory_swap if memory is not specified + memory: 32M + memory_swap: 64M + state: started + debug: true + register: memory_swap_1 + +- name: memory_swap (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + # Docker daemon does not accept memory_swap if memory is not specified + memory: 32M + memory_swap: 64M + state: started + debug: true + register: memory_swap_2 + +- name: memory_swap (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + # Docker daemon does not accept memory_swap if memory is not specified + memory: 32M + memory_swap: 48M + state: started + force_kill: true + debug: true + register: memory_swap_3 + +- name: memory_swap (unlimited) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + # Docker daemon does not accept memory_swap if memory is not specified + memory: 32M + memory_swap: unlimited + state: started + force_kill: true + debug: true + register: memory_swap_4 + +- name: memory_swap (unlimited via -1) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + # Docker daemon does not accept memory_swap if memory is not specified + memory: 32M + memory_swap: -1 + state: started + force_kill: true + debug: true + register: memory_swap_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - memory_swap_1 is changed + # Sometimes (in particular during integration tests, maybe when not running + # on a proper VM), memory_swap cannot be set and will be -1 afterwards. + - memory_swap_2 is not changed or memory_swap_2.container.HostConfig.MemorySwap == -1 + - memory_swap_3 is changed + # Unlimited memory_swap (using 'unlimited') should be allowed + # (If the value was already -1 because of the above reasons, it won't change) + - (memory_swap_4 is changed or memory_swap_3.container.HostConfig.MemorySwap == -1) and memory_swap_4.container.HostConfig.MemorySwap == -1 + # Unlimited memory_swap (using '-1') should be allowed + - memory_swap_5 is not changed and memory_swap_5.container.HostConfig.MemorySwap == -1 + +- debug: var=memory_swap_1 + when: memory_swap_2 is changed +- debug: var=memory_swap_2 + when: memory_swap_2 is changed +- debug: var=memory_swap_3 + when: memory_swap_2 is changed + +#################################################################### +## memory_swappiness ############################################### +#################################################################### + +- name: memory_swappiness + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory_swappiness: 40 + state: started + register: memory_swappiness_1 + +- name: memory_swappiness (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory_swappiness: 40 + state: started + register: memory_swappiness_2 + +- name: memory_swappiness (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + memory_swappiness: 60 + state: started + force_kill: true + register: memory_swappiness_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - memory_swappiness_1 is changed + - memory_swappiness_2 is not changed + - memory_swappiness_3 is changed + when: "'Docker warning: Your kernel does not support memory swappiness capabilities or the cgroup is not mounted. Memory swappiness discarded.' not in (memory_swappiness_1.warnings | default([]))" + +#################################################################### +## oom_killer ###################################################### +#################################################################### + +- name: oom_killer + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + oom_killer: true + state: started + register: oom_killer_1 + +- name: oom_killer (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + oom_killer: true + state: started + register: oom_killer_2 + +- name: oom_killer (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + oom_killer: false + state: started + force_kill: true + register: oom_killer_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - oom_killer_1 is changed + - oom_killer_2 is not changed + - oom_killer_3 is changed + when: "'Docker warning: Your kernel does not support OomKillDisable. OomKillDisable discarded.' not in (oom_killer_1.warnings | default([]))" + +#################################################################### +## oom_score_adj ################################################### +#################################################################### + +- name: oom_score_adj + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + oom_score_adj: 5 + state: started + register: oom_score_adj_1 + +- name: oom_score_adj (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + oom_score_adj: 5 + state: started + register: oom_score_adj_2 + +- name: oom_score_adj (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + oom_score_adj: 7 + state: started + force_kill: true + register: oom_score_adj_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - oom_score_adj_1 is changed + - oom_score_adj_2 is not changed + - oom_score_adj_3 is changed + when: "'Docker warning: Your kernel does not support OomScoreAdj. OomScoreAdj discarded.' not in (oom_score_adj_1.warnings | default([]))" + +#################################################################### +## output_logs ##################################################### +#################################################################### + +# TODO: - output_logs + +#################################################################### +## paused ########################################################## +#################################################################### + +- name: paused + docker_container: + image: "{{ docker_test_image_alpine }}" + command: "/bin/sh -c 'sleep 10m'" + name: "{{ cname }}" + state: started + paused: true + force_kill: true + register: paused_1 + +- name: inspect paused + command: "docker inspect -f {% raw %}'{{.State.Status}} {{.State.Paused}}'{% endraw %} {{ cname }}" + register: paused_2 + +- name: paused (idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: "/bin/sh -c 'sleep 10m'" + name: "{{ cname }}" + state: started + paused: true + force_kill: true + register: paused_3 + +- name: paused (continue) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: "/bin/sh -c 'sleep 10m'" + name: "{{ cname }}" + state: started + paused: false + force_kill: true + register: paused_4 + +- name: inspect paused + command: "docker inspect -f {% raw %}'{{.State.Status}} {{.State.Paused}}'{% endraw %} {{ cname }}" + register: paused_5 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - paused_1 is changed + - 'paused_2.stdout == "paused true"' + - paused_3 is not changed + - paused_4 is changed + - 'paused_5.stdout == "running false"' + +#################################################################### +## pid_mode ######################################################## +#################################################################### + +- name: start helpers + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname_h1 }}" + state: started + register: pid_mode_helper + +- name: pid_mode + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + pid_mode: "container:{{ pid_mode_helper.container.Id }}" + register: pid_mode_1 + +- name: pid_mode (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + pid_mode: "container:{{ cname_h1 }}" + register: pid_mode_2 + +- name: pid_mode (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + pid_mode: host + force_kill: true + register: pid_mode_3 + +- name: cleanup + docker_container: + name: "{{ container_name }}" + state: absent + force_kill: true + loop: + - "{{ cname }}" + - "{{ cname_h1 }}" + loop_control: + loop_var: container_name + diff: false + +- assert: + that: + - pid_mode_1 is changed + - pid_mode_2 is not changed + - pid_mode_3 is changed + +#################################################################### +## pids_limit ###################################################### +#################################################################### + +- name: pids_limit + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + pids_limit: 10 + register: pids_limit_1 + +- name: pids_limit (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + pids_limit: 10 + register: pids_limit_2 + +- name: pids_limit (changed) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + pids_limit: 20 + force_kill: true + register: pids_limit_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - pids_limit_1 is changed + - pids_limit_2 is not changed + - pids_limit_3 is changed + +#################################################################### +## platform ######################################################## +#################################################################### + +- name: Remove hello-world image + docker_image: + name: hello-world:latest + state: absent + +- name: platform + docker_container: + image: hello-world:latest + name: "{{ cname }}" + state: present + pull: true + platform: linux/amd64 + debug: true + register: platform_1 + ignore_errors: true + +- name: platform (idempotency) + docker_container: + image: hello-world:latest + name: "{{ cname }}" + state: present + # The container always reports 'linux' as platform instead of 'linux/amd64'... + platform: linux + debug: true + register: platform_2 + ignore_errors: true + +- name: platform (changed) + docker_container: + image: hello-world:latest + name: "{{ cname }}" + state: present + pull: true + platform: linux/386 + force_kill: true + debug: true + comparisons: + # Do not restart because of the changed image ID + image: ignore + register: platform_3 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - platform_1 is changed + - platform_2 is not changed and platform_2 is not failed + - platform_3 is changed + when: docker_api_version is version('1.41', '>=') +- assert: + that: + - platform_1 is failed + - | + ('API version is ' ~ docker_api_version ~ '.') in platform_1.msg and 'Minimum version required is 1.41 ' in platform_1.msg + when: docker_api_version is version('1.41', '<') + +#################################################################### +## privileged ###################################################### +#################################################################### + +- name: privileged + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + privileged: true + state: started + register: privileged_1 + +- name: privileged (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + privileged: true + state: started + register: privileged_2 + +- name: privileged (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + privileged: false + state: started + force_kill: true + register: privileged_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - privileged_1 is changed + - privileged_2 is not changed + - privileged_3 is changed + +#################################################################### +## published_ports and default_host_ip ############################# +#################################################################### + +- name: published_ports + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '9001' + - '9002' + register: published_ports_1 + +- name: published_ports (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '9002' + - '9001' + register: published_ports_2 + +- name: published_ports (less published_ports) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '9002' + register: published_ports_3 + +- name: published_ports (more published_ports) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '9002' + - '9003' + force_kill: true + register: published_ports_4 + +- name: published_ports (ports with IP addresses) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '127.0.0.1:9002:9002/tcp' + - '[::1]:9003:9003/tcp' + - '[fe80::1%test]:90:90/tcp' + force_kill: true + register: published_ports_5 + +- name: published_ports (ports with IP addresses, idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '127.0.0.1:9002:9002/tcp' + - '[::1]:9003:9003/tcp' + - '[fe80::1%test]:90:90/tcp' + register: published_ports_6 + +- name: published_ports (no published ports) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: [] + comparisons: + published_ports: strict + force_kill: true + register: published_ports_7 + +- name: published_ports (default_host_ip not set) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '9001' + - '9002' + force_kill: true + register: published_ports_8 + +- name: published_ports (default_host_ip set to empty string) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '9002' + - '9001' + default_host_ip: '' + force_kill: true + register: published_ports_9 + +- name: published_ports (default_host_ip set to empty string, idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '9002' + - '9001' + default_host_ip: '' + register: published_ports_10 + +- name: published_ports (default_host_ip unset) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - '9002' + - '9001' + force_kill: true + register: published_ports_11 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - published_ports_1 is changed + - published_ports_2 is not changed + - published_ports_3 is not changed + - published_ports_4 is changed + - published_ports_5 is changed + - published_ports_6 is not changed + - published_ports_7 is changed + - published_ports_8 is changed + - published_ports_9 is changed + - published_ports_10 is not changed + - published_ports_11 is changed + +#################################################################### +## pull ############################################################ +#################################################################### + +# TODO: - pull + +#################################################################### +## read_only ####################################################### +#################################################################### + +- name: read_only + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + read_only: true + state: started + register: read_only_1 + +- name: read_only (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + read_only: true + state: started + register: read_only_2 + +- name: read_only (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + read_only: false + state: started + force_kill: true + register: read_only_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - read_only_1 is changed + - read_only_2 is not changed + - read_only_3 is changed + +#################################################################### +## restart_policy ################################################## +#################################################################### + +- name: restart_policy + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + restart_policy: always + state: started + register: restart_policy_1 + +- name: restart_policy (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + restart_policy: always + state: started + register: restart_policy_2 + +- name: restart_policy (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + restart_policy: unless-stopped + state: started + force_kill: true + register: restart_policy_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - restart_policy_1 is changed + - restart_policy_2 is not changed + - restart_policy_3 is changed + +#################################################################### +## restart_retries ################################################# +#################################################################### + +- name: restart_retries + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + restart_policy: on-failure + restart_retries: 5 + state: started + register: restart_retries_1 + +- name: restart_retries (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + restart_policy: on-failure + restart_retries: 5 + state: started + register: restart_retries_2 + +- name: restart_retries (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + restart_policy: on-failure + restart_retries: 2 + state: started + force_kill: true + register: restart_retries_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - restart_retries_1 is changed + - restart_retries_2 is not changed + - restart_retries_3 is changed + +#################################################################### +## runtime ######################################################### +#################################################################### + +- name: runtime + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + runtime: runc + state: started + register: runtime_1 + +- name: runtime (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + runtime: runc + state: started + register: runtime_2 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - runtime_1 is changed + - runtime_2 is not changed + +#################################################################### +## security_opts ################################################### +#################################################################### + +# In case some of the options stop working, here are some more +# options which *currently* work with all integration test targets: +# no-new-privileges +# label:disable +# label=disable +# label:level:s0:c100,c200 +# label=level:s0:c100,c200 +# label:type:svirt_apache_t +# label=type:svirt_apache_t +# label:user:root +# label=user:root +# seccomp:unconfined +# seccomp=unconfined +# apparmor:docker-default +# apparmor=docker-default + +- name: security_opts + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + security_opts: + - "label:level:s0:c100,c200" + - "no-new-privileges" + register: security_opts_1 + +- name: security_opts (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + security_opts: + - "no-new-privileges" + - "label:level:s0:c100,c200" + register: security_opts_2 + +- name: security_opts (less security options) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + security_opts: + - "no-new-privileges" + register: security_opts_3 + +- name: security_opts (more security options) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + security_opts: + - "label:disable" + - "no-new-privileges" + force_kill: true + register: security_opts_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - security_opts_1 is changed + - security_opts_2 is not changed + - security_opts_3 is not changed + - security_opts_4 is changed + +#################################################################### +## shm_size ######################################################## +#################################################################### + +- name: shm_size + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + shm_size: 96M + state: started + register: shm_size_1 + +- name: shm_size (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + shm_size: 96M + state: started + register: shm_size_2 + +- name: shm_size (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + shm_size: 75M + state: started + force_kill: true + register: shm_size_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - shm_size_1 is changed + - shm_size_2 is not changed + - shm_size_3 is changed + +#################################################################### +## stop_signal ##################################################### +#################################################################### + +- name: stop_signal + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + stop_signal: "30" + state: started + register: stop_signal_1 + +- name: stop_signal (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + stop_signal: "30" + state: started + register: stop_signal_2 + +- name: stop_signal (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + stop_signal: "9" + state: started + force_kill: true + register: stop_signal_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - stop_signal_1 is changed + - stop_signal_2 is not changed + - stop_signal_3 is changed + +#################################################################### +## stop_timeout #################################################### +#################################################################### + +- name: stop_timeout + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + stop_timeout: 2 + state: started + register: stop_timeout_1 + +- name: stop_timeout (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + stop_timeout: 2 + state: started + register: stop_timeout_2 + +- name: stop_timeout (no change) + # stop_timeout changes are ignored by default + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + stop_timeout: 1 + state: started + register: stop_timeout_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - stop_timeout_1 is changed + - stop_timeout_2 is not changed + - stop_timeout_3 is not changed + +#################################################################### +## storage_opts #################################################### +#################################################################### + +- name: storage_opts + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + storage_opts: + size: 12m + state: started + register: storage_opts_1 + ignore_errors: true + +- name: storage_opts (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + storage_opts: + size: 12m + state: started + register: storage_opts_2 + ignore_errors: true + +- name: storage_opts (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + storage_opts: + size: 24m + state: started + force_kill: true + register: storage_opts_3 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - storage_opts_1 is changed + - storage_opts_2 is not failed and storage_opts_2 is not changed + - storage_opts_3 is not failed and storage_opts_3 is changed + when: storage_opts_1 is not failed + +- assert: + that: + - "'is supported only for' in storage_opts_1.msg" + - storage_opts_2 is failed + - storage_opts_3 is failed + when: storage_opts_1 is failed + +#################################################################### +## sysctls ######################################################### +#################################################################### + +# In case some of the options stop working, here are some more +# options which *currently* work with all integration test targets: +# net.ipv4.conf.default.log_martians: 1 +# net.ipv4.conf.default.secure_redirects: 0 +# net.ipv4.conf.default.send_redirects: 0 +# net.ipv4.conf.all.log_martians: 1 +# net.ipv4.conf.all.accept_redirects: 0 +# net.ipv4.conf.all.secure_redirects: 0 +# net.ipv4.conf.all.send_redirects: 0 + +- name: sysctls + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + sysctls: + net.ipv4.icmp_echo_ignore_all: 1 + net.ipv4.ip_forward: 1 + register: sysctls_1 + +- name: sysctls (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + sysctls: + net.ipv4.ip_forward: 1 + net.ipv4.icmp_echo_ignore_all: 1 + register: sysctls_2 + +- name: sysctls (less sysctls) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + sysctls: + net.ipv4.icmp_echo_ignore_all: 1 + register: sysctls_3 + +- name: sysctls (more sysctls) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + sysctls: + net.ipv4.icmp_echo_ignore_all: 1 + net.ipv6.conf.default.accept_redirects: 0 + force_kill: true + register: sysctls_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - sysctls_1 is changed + - sysctls_2 is not changed + - sysctls_3 is not changed + - sysctls_4 is changed + +#################################################################### +## tmpfs ########################################################### +#################################################################### + +- name: tmpfs + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + tmpfs: + - "/test1:rw,noexec,nosuid,size=65536k" + - "/test2:rw,noexec,nosuid,size=65536k" + register: tmpfs_1 + +- name: tmpfs (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + tmpfs: + - "/test2:rw,noexec,nosuid,size=65536k" + - "/test1:rw,noexec,nosuid,size=65536k" + register: tmpfs_2 + +- name: tmpfs (less tmpfs) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + tmpfs: + - "/test1:rw,noexec,nosuid,size=65536k" + register: tmpfs_3 + +- name: tmpfs (more tmpfs) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + tmpfs: + - "/test1:rw,noexec,nosuid,size=65536k" + - "/test3:rw,noexec,nosuid,size=65536k" + force_kill: true + register: tmpfs_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - tmpfs_1 is changed + - tmpfs_2 is not changed + - tmpfs_3 is not changed + - tmpfs_4 is changed + +#################################################################### +## tty ############################################################# +#################################################################### + +- name: tty + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + tty: true + state: started + register: tty_1 + ignore_errors: true + +- name: tty (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + tty: true + state: started + register: tty_2 + ignore_errors: true + +- name: tty (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + tty: false + state: started + force_kill: true + register: tty_3 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - tty_1 is changed + - tty_2 is not changed and tty_2 is not failed + - tty_3 is changed + when: tty_1 is not failed + +- assert: + that: + - "'error during container init: open /dev/pts/' in tty_1.msg" + - "': operation not permitted: ' in tty_1.msg" + when: tty_1 is failed + +#################################################################### +## ulimits ######################################################### +#################################################################### + +- name: ulimits + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + ulimits: + - "nofile:1234:1234" + - "nproc:3:6" + register: ulimits_1 + +- name: ulimits (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + ulimits: + - "nproc:3:6" + - "nofile:1234:1234" + register: ulimits_2 + +- name: ulimits (less ulimits) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + ulimits: + - "nofile:1234:1234" + register: ulimits_3 + +- name: ulimits (more ulimits) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + ulimits: + - "nofile:1234:1234" + - "sigpending:100:200" + force_kill: true + register: ulimits_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - ulimits_1 is changed + - ulimits_2 is not changed + - ulimits_3 is not changed + - ulimits_4 is changed + +#################################################################### +## user ############################################################ +#################################################################### + +- name: user + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + user: nobody + state: started + register: user_1 + +- name: user (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + user: nobody + state: started + register: user_2 + +- name: user (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + user: root + state: started + force_kill: true + register: user_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - user_1 is changed + - user_2 is not changed + - user_3 is changed + +#################################################################### +## userns_mode ##################################################### +#################################################################### + +- name: userns_mode + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + userns_mode: host + state: started + register: userns_mode_1 + +- name: userns_mode (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + userns_mode: host + state: started + register: userns_mode_2 + +- name: userns_mode (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + userns_mode: "" + state: started + force_kill: true + register: userns_mode_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - userns_mode_1 is changed + - userns_mode_2 is not changed + - userns_mode_3 is changed + +#################################################################### +## uts ############################################################# +#################################################################### + +- name: uts + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + uts: host + state: started + register: uts_1 + +- name: uts (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + uts: host + state: started + register: uts_2 + +- name: uts (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + uts: "" + state: started + force_kill: true + register: uts_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - uts_1 is changed + - uts_2 is not changed + - uts_3 is changed + +#################################################################### +## working_dir ##################################################### +#################################################################### + +- name: working_dir + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + working_dir: /tmp + state: started + register: working_dir_1 + +- name: working_dir (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + working_dir: /tmp + state: started + register: working_dir_2 + +- name: working_dir (change) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + working_dir: / + state: started + force_kill: true + register: working_dir_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - working_dir_1 is changed + - working_dir_2 is not changed + - working_dir_3 is changed + +#################################################################### +#################################################################### +#################################################################### diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/ports.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/ports.yml new file mode 100644 index 000000000..ced868513 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/ports.yml @@ -0,0 +1,326 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- 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: error handling ################################# +#################################################################### + +- name: published_ports -- non-closing square bracket + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - "[::1:2000:3000" + register: published_ports_1 + ignore_errors: true + +- name: published_ports -- forgot square brackets for IPv6 + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - "::1:2000:3000" + register: published_ports_2 + ignore_errors: true + +- name: published_ports -- disallow hostnames + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - "foo:2000:3000" + register: published_ports_3 + ignore_errors: true + +- assert: + that: + - published_ports_1 is failed + - published_ports_1.msg == 'Cannot find closing "]" in input "[::1:2000:3000" for opening "[" at index 1!' + - published_ports_2 is failed + - published_ports_2.msg == 'Invalid port description "::1:2000:3000" - expected 1 to 3 colon-separated parts, but got 5. Maybe you forgot to use square brackets ([...]) around an IPv6 address?' + - published_ports_3 is failed + - "published_ports_3.msg == 'Bind addresses for published ports must be IPv4 or IPv6 addresses, not hostnames. Use the dig lookup to resolve hostnames. (Found hostname: foo)'" + +#################################################################### +## published_ports: port range ##################################### +#################################################################### + +- name: published_ports -- port range + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + exposed_ports: + - "9001" + - "9010-9050" + published_ports: + - "9001:9001" + - "9010-9050:9010-9050" + force_kill: true + register: published_ports_1 + +- name: published_ports -- port range (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + exposed_ports: + - "9001" + - "9010-9050" + published_ports: + - "9001:9001" + - "9010-9050:9010-9050" + force_kill: true + register: published_ports_2 + +- name: published_ports -- port range (different range) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + exposed_ports: + - "9001" + - "9010-9050" + published_ports: + - "9001:9001" + - "9020-9060:9020-9060" + force_kill: true + register: published_ports_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - published_ports_1 is changed + - 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: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ item }}" + state: started + published_ports: + - "9010-9050:9010" + force_kill: true + loop: + - '{{ cname }}' + - '{{ cname2 }}' + register: published_ports_1 + +- name: published_ports -- one-element container port range (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ item }}" + state: started + published_ports: + - "9010-9050:9010" + force_kill: true + loop: + - '{{ cname }}' + - '{{ cname2 }}' + register: published_ports_2 + +- name: published_ports -- one-element container port range (different range) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ item }}" + state: started + published_ports: + - "9010-9051:9010" + force_kill: true + loop: + - '{{ cname }}' + - '{{ cname2 }}' + register: published_ports_3 + +- name: cleanup + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + loop: + - '{{ cname }}' + - '{{ cname2 }}' + diff: false + +- 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: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - "[::1]:9001:9001" + force_kill: true + register: published_ports_1 + +- name: published_ports -- IPv6 (idempotency) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - "[::1]:9001:9001" + force_kill: true + register: published_ports_2 + +- name: published_ports -- IPv6 (different IP) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - "127.0.0.1:9001:9001" + force_kill: true + register: published_ports_3 + +- name: published_ports -- IPv6 (hostname) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + published_ports: + - "localhost:9001:9001" + force_kill: true + register: published_ports_4 + ignore_errors: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - published_ports_1 is changed + - published_ports_2 is not changed + - published_ports_3 is changed + - published_ports_4 is failed + +#################################################################### +## publish_all_ports ############################################### +#################################################################### + +- set_fact: + publish_all_ports_test_cases: + - test_name: no_options + changed: true + - test_name: null_to_true + publish_all_ports_value: true + changed: true + - test_name: true_idempotency + publish_all_ports_value: true + changed: false + - test_name: true_to_null + changed: false + - test_name: null_to_true_2 + publish_all_ports_value: true + changed: false + - test_name: true_to_false + publish_all_ports_value: false + changed: true + - test_name: false_idempotency + publish_all_ports_value: false + changed: false + - test_name: false_to_null + changed: false + - test_name: null_with_published_ports + published_ports_value: &ports + - "9001:9001" + - "9010-9050:9010-9050" + changed: true + - test_name: null_to_true_with_published_ports + publish_all_ports_value: true + published_ports_value: *ports + changed: true + - test_name: true_idempotency_with_published_ports + publish_all_ports_value: true + published_ports_value: *ports + changed: false + - test_name: true_to_null_with_published_ports + published_ports_value: *ports + changed: false + - test_name: null_to_true_2_with_published_ports + publish_all_ports_value: true + published_ports_value: *ports + changed: false + - test_name: true_to_false_with_published_ports + publish_all_ports_value: false + published_ports_value: *ports + changed: true + - test_name: false_idempotency_with_published_ports + publish_all_ports_value: false + published_ports_value: *ports + changed: false + - test_name: false_to_null_with_published_ports + published_ports_value: *ports + changed: false + +- name: publish_all_ports ({{ test_case.test_name }}) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + publish_all_ports: "{{ test_case.publish_all_ports_value | default(omit) }}" + published_ports: "{{ test_case.published_ports_value | default(omit) }}" + force_kill: true + register: publish_all_ports + loop_control: + loop_var: test_case + loop: "{{ publish_all_ports_test_cases }}" + +- assert: + that: + - publish_all_ports.results[index].changed == test_case.changed + loop: "{{ publish_all_ports_test_cases }}" + loop_control: + index_var: index + loop_var: test_case diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/regression-45700-dont-parse-on-absent.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/regression-45700-dont-parse-on-absent.yml new file mode 100644 index 000000000..928463aea --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/regression-45700-dont-parse-on-absent.yml @@ -0,0 +1,38 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Regression test for https://github.com/ansible/ansible/pull/45700 +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-45700' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname] }}" + +- name: Start container + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + +- name: Stop container with a lot of invalid options + docker_container: + name: "{{ cname }}" + force_kill: true + # Some options with "invalid" values, which would + # have to be parsed. The values are "invalid" because + # the containers and networks listed here do not exist. + # This can happen because the networks are removed + # before the container is stopped (see + # https://github.com/ansible/ansible/issues/45486). + networks: + - name: "nonexistant-network-{{ (2**32) | random }}" + published_ports: + - '1:2' + - '3' + links: + - "nonexistant-container-{{ (2**32) | random }}:test" + state: absent diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/start-stop.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/start-stop.yml new file mode 100644 index 000000000..97ac38a54 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/start-stop.yml @@ -0,0 +1,459 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-hi' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname] }}" + +#################################################################### +## Creation ######################################################## +#################################################################### + +- name: Create container (check) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: present + check_mode: true + register: create_1 + +- name: Create container + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: present + register: create_2 + +- name: Create container (idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: present + register: create_3 + +- name: Create container (idempotent check) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: present + check_mode: true + register: create_4 + +- assert: + that: + - create_1 is changed + - create_2 is changed + - create_3 is not changed + - create_4 is not changed + +#################################################################### +## Starting (after creation) ####################################### +#################################################################### + +- name: Start container (check) + docker_container: + name: "{{ cname }}" + state: started + check_mode: true + register: start_1 + +- name: Start container + docker_container: + name: "{{ cname }}" + state: started + register: start_2 + +- name: Start container (idempotent) + docker_container: + name: "{{ cname }}" + state: started + register: start_3 + +- name: Start container (idempotent check) + docker_container: + name: "{{ cname }}" + state: started + check_mode: true + register: start_4 + +- assert: + that: + - start_1 is changed + - start_2 is changed + - start_3 is not changed + - start_4 is not changed + +#################################################################### +## Present check for running container ############################# +#################################################################### + +- name: Present check for running container (check) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: present + check_mode: true + register: present_check_1 + +- name: Present check for running container + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: present + register: present_check_2 + +- assert: + that: + - present_check_1 is not changed + - present_check_2 is not changed + +#################################################################### +## Starting (from scratch) ######################################### +#################################################################### + +- name: Remove container (setup for starting from scratch) + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + +- name: Start container from scratch (check) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + stop_timeout: 1 + name: "{{ cname }}" + state: started + check_mode: true + register: start_scratch_1 + +- name: Start container from scratch + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + stop_timeout: 1 + name: "{{ cname }}" + state: started + register: start_scratch_2 + +- name: Start container from scratch (idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + stop_timeout: 1 + name: "{{ cname }}" + state: started + register: start_scratch_3 + +- name: Start container from scratch (idempotent check) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + stop_timeout: 1 + name: "{{ cname }}" + state: started + check_mode: true + register: start_scratch_4 + +- assert: + that: + - start_scratch_1 is changed + - start_scratch_2 is changed + - start_scratch_3 is not changed + - start_scratch_4 is not changed + +#################################################################### +## Recreating ###################################################### +#################################################################### + +- name: Recreating container (created) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: present + force_kill: true + register: recreate_1 + +- name: Recreating container (created, recreate, check mode) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + recreate: true + state: present + force_kill: true + register: recreate_2 + check_mode: true + +- name: Recreating container (created, recreate) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + recreate: true + state: present + force_kill: true + register: recreate_3 + +- name: Recreating container (started) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + force_kill: true + register: recreate_4 + +- name: Recreating container (started, recreate, check mode) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + recreate: true + removal_wait_timeout: 10 + state: started + force_kill: true + register: recreate_5 + check_mode: true + +- name: Recreating container (started, recreate) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + recreate: true + removal_wait_timeout: 10 + state: started + force_kill: true + register: recreate_6 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- debug: var=recreate_1 +- debug: var=recreate_3 +- debug: var=recreate_4 +- debug: var=recreate_6 + +- assert: + that: + - recreate_2 is changed + - recreate_3 is changed + - recreate_4 is changed + - recreate_5 is changed + - recreate_6 is changed + - recreate_1.container.Id == recreate_2.container.Id + - recreate_1.container.Id != recreate_3.container.Id + - recreate_3.container.Id == recreate_4.container.Id + - recreate_4.container.Id == recreate_5.container.Id + - recreate_4.container.Id != recreate_6.container.Id + +#################################################################### +## Restarting ###################################################### +#################################################################### + +- name: Restarting + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + stop_timeout: 1 + volumes: + - /tmp/tmp + register: restart_1 + +- name: Restarting (restart, check mode) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + restart: true + state: started + stop_timeout: 1 + force_kill: true + register: restart_2 + check_mode: true + +- name: Restarting (restart) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + restart: true + state: started + stop_timeout: 1 + force_kill: true + register: restart_3 + +- name: Restarting (verify volumes) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + stop_timeout: 1 + volumes: + - /tmp/tmp + register: restart_4 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- assert: + that: + - restart_1 is changed + - restart_2 is changed + - restart_3 is changed + - restart_1.container.Id == restart_3.container.Id + - restart_4 is not changed + +#################################################################### +## Stopping ######################################################## +#################################################################### + +- name: Stop container (check) + docker_container: + image: "{{ docker_test_image_alpine }}" + name: "{{ cname }}" + state: stopped + stop_timeout: 1 + check_mode: true + register: stop_1 + +- name: Stop container + docker_container: + image: "{{ docker_test_image_alpine }}" + name: "{{ cname }}" + state: stopped + stop_timeout: 1 + register: stop_2 + +- name: Stop container (idempotent) + docker_container: + image: "{{ docker_test_image_alpine }}" + name: "{{ cname }}" + state: stopped + stop_timeout: 1 + register: stop_3 + +- name: Stop container (idempotent check) + docker_container: + image: "{{ docker_test_image_alpine }}" + name: "{{ cname }}" + state: stopped + stop_timeout: 1 + check_mode: true + register: stop_4 + +- assert: + that: + - stop_1 is changed + - stop_2 is changed + - stop_3 is not changed + - stop_4 is not changed + +#################################################################### +## Removing ######################################################## +#################################################################### + +- name: Remove container (check) + docker_container: + name: "{{ cname }}" + state: absent + check_mode: true + register: remove_1 + +- name: Remove container + docker_container: + name: "{{ cname }}" + state: absent + register: remove_2 + +- name: Remove container (idempotent) + docker_container: + name: "{{ cname }}" + state: absent + register: remove_3 + +- name: Remove container (idempotent check) + docker_container: + name: "{{ cname }}" + state: absent + check_mode: true + register: remove_4 + +- assert: + that: + - remove_1 is changed + - remove_2 is changed + - remove_3 is not changed + - remove_4 is not changed + +#################################################################### +## Removing (from running) ######################################### +#################################################################### + +- name: Start container (setup for removing from running) + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + +- name: Remove container from running (check) + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + check_mode: true + register: remove_from_running_1 + +- name: Remove container from running + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + register: remove_from_running_2 + +- name: Remove container from running (idempotent) + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + register: remove_from_running_3 + +- name: Remove container from running (idempotent check) + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + check_mode: true + register: remove_from_running_4 + +- assert: + that: + - remove_from_running_1 is changed + - remove_from_running_2 is changed + - remove_from_running_3 is not changed + - remove_from_running_4 is not changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/update.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/update.yml new file mode 100644 index 000000000..a180e0f55 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container/tasks/tests/update.yml @@ -0,0 +1,212 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-update' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname] }}" + +# We do not test cpuset_cpus and cpuset_mems since changing it fails if the system does +# not have 'enough' CPUs. We do not test kernel_memory since it is deprecated and fails. + +- set_fact: + has_blkio_weight: true + +- name: Create container + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + blkio_weight: "{{ 123 if has_blkio_weight else omit }}" + cpu_period: 90000 + cpu_quota: 150000 + cpu_shares: 900 + memory: 64M + memory_reservation: 64M + memory_swap: 64M + restart_policy: on-failure + restart_retries: 5 + register: create + ignore_errors: true + +- when: create is failed + block: + - name: Make sure container is not there + docker_container: + name: "{{ cname }}" + state: absent + + - when: "'setting cgroup config for procHooks process caused: failed to write' in create.msg and 'io.bfq.weight' in create.msg" + set_fact: + has_blkio_weight: false + + - name: Create container again + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + blkio_weight: "{{ 123 if has_blkio_weight else omit }}" + cpu_period: 90000 + cpu_quota: 150000 + cpu_shares: 900 + memory: 64M + memory_reservation: 64M + memory_swap: 64M + restart_policy: on-failure + restart_retries: 5 + register: create_2 + + - when: "'setting cgroup config for procHooks process caused: failed to write' in create.msg and 'io.bfq.weight' in create.msg" + set_fact: + create: "{{ create_2 }}" + +- name: Update values + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + blkio_weight: "{{ 234 if has_blkio_weight else omit }}" + cpu_period: 50000 + cpu_quota: 50000 + cpu_shares: 1100 + memory: 48M + memory_reservation: 48M + memory_swap: unlimited + restart_policy: on-failure # only on-failure can have restart_retries, so don't change it here + restart_retries: 2 + register: update + diff: true + +- name: Update values again + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + state: started + blkio_weight: "{{ 135 if has_blkio_weight else omit }}" + cpu_period: 30000 + cpu_quota: 40000 + cpu_shares: 1000 + memory: 32M + memory_reservation: 30M + memory_swap: 128M + restart_policy: always + restart_retries: 0 + register: update2 + diff: true + +- name: Recreate container + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 20m"' # this will force re-creation + name: "{{ cname }}" + state: started + blkio_weight: "{{ 234 if has_blkio_weight else omit }}" + cpu_period: 50000 + cpu_quota: 50000 + cpu_shares: 1100 + memory: 48M + memory_reservation: 48M + memory_swap: unlimited + restart_policy: on-failure + restart_retries: 2 + force_kill: true + register: recreate + diff: true + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + diff: false + +- name: Check general things + assert: + that: + - create is changed + - update is changed + - update2 is changed + - recreate is changed + + # Make sure the container was *not* recreated when it should not be + - create.container.Id == update.container.Id + - create.container.Id == update2.container.Id + + # Make sure that the container was recreated when it should be + - create.container.Id != recreate.container.Id + +- name: Check diff for first update + assert: + that: + # blkio_weight sometimes cannot be set, then we end up with 0 instead of the value we had + - not has_blkio_weight or update.diff.before.blkio_weight == 123 or 'Docker warning: Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.' in (create.warnings | default([])) + - not has_blkio_weight or update.diff.after.blkio_weight == 234 + - update.diff.before.cpu_period == 90000 + - update.diff.after.cpu_period == 50000 + - update.diff.before.cpu_quota == 150000 + - update.diff.after.cpu_quota == 50000 + - update.diff.before.cpu_shares == 900 + - update.diff.after.cpu_shares == 1100 + - update.diff.before.memory == 67108864 + - update.diff.after.memory == 50331648 + - update.diff.before.memory_reservation == 67108864 + - update.diff.after.memory_reservation == 50331648 + - (update.diff.before.memory_swap | default(0)) == 67108864 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([])) + - (update.diff.after.memory_swap | default(0)) == -1 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([])) + - "'restart_policy' not in update.diff.before" + - update.diff.before.restart_retries == 5 + - update.diff.after.restart_retries == 2 + +- name: Check diff for second update + assert: + that: + - not has_blkio_weight or update2.diff.before.blkio_weight == 234 or 'Docker warning: Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.' in (create.warnings | default([])) + - not has_blkio_weight or update2.diff.after.blkio_weight == 135 + - update2.diff.before.cpu_period == 50000 + - update2.diff.after.cpu_period == 30000 + - update2.diff.before.cpu_quota == 50000 + - update2.diff.after.cpu_quota == 40000 + - update2.diff.before.cpu_shares == 1100 + - update2.diff.after.cpu_shares == 1000 + - update2.diff.before.memory == 50331648 + - update2.diff.after.memory == 33554432 + - update2.diff.before.memory_reservation == 50331648 + - update2.diff.after.memory_reservation == 31457280 + - (update2.diff.before.memory_swap | default(0)) == -1 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([])) + - (update2.diff.after.memory_swap | default(0)) == 134217728 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([])) + - update2.diff.before.restart_policy == 'on-failure' + - update2.diff.after.restart_policy == 'always' + - update2.diff.before.restart_retries == 2 + - update2.diff.after.restart_retries == 0 + +- name: Check diff for recreation + assert: + that: + - not has_blkio_weight or recreate.diff.before.blkio_weight == 135 or 'Docker warning: Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.' in (create.warnings | default([])) + - not has_blkio_weight or recreate.diff.after.blkio_weight == 234 + - recreate.diff.before.cpu_period == 30000 + - recreate.diff.after.cpu_period == 50000 + - recreate.diff.before.cpu_quota == 40000 + - recreate.diff.after.cpu_quota == 50000 + - recreate.diff.before.cpu_shares == 1000 + - recreate.diff.after.cpu_shares == 1100 + - recreate.diff.before.memory == 33554432 + - recreate.diff.after.memory == 50331648 + - recreate.diff.before.memory_reservation == 31457280 + - recreate.diff.after.memory_reservation == 50331648 + - (recreate.diff.before.memory_swap | default(0)) == 134217728 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([])) + - (recreate.diff.after.memory_swap | default(0)) == -1 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([])) + - recreate.diff.before.restart_policy == 'always' + - recreate.diff.after.restart_policy == 'on-failure' + - recreate.diff.before.restart_retries == 0 + - recreate.diff.after.restart_retries == 2 + - recreate.diff.before.command == ['/bin/sh', '-c', 'sleep 10m'] + - recreate.diff.after.command == ['/bin/sh', '-c', 'sleep 20m'] diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/meta/main.yml new file mode 100644 index 000000000..2650229d8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/main.yml new file mode 100644 index 000000000..20f9a2681 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/main.yml @@ -0,0 +1,45 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Gather facts on controller + setup: + gather_subset: '!all' + delegate_to: localhost + delegate_facts: true + run_once: true + +# Create random name prefix (for containers) +- name: Create random container name prefix + set_fact: + cname_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + cnames: [] + +- debug: + msg: "Using container name prefix {{ cname_prefix }}" + +# Run the tests +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: "Make sure all containers are removed" + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + with_items: "{{ cnames }}" + diff: false + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old Docker API version to run all docker_container_copy_into tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/tests/content.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/tests/content.yml new file mode 100644 index 000000000..b49c05a97 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/tests/content.yml @@ -0,0 +1,1197 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-c' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname] }}" + +# Create container + +- name: Create container + docker_container: + image: "{{ docker_test_image_alpine }}" + command: + - /bin/sh + - "-c" + - >- + mkdir /dir; + ln -s file /lnk; + ln -s lnk3 /lnk2; + ln -s lnk2 /lnk1; + sleep 10m; + name: "{{ cname }}" + state: started + +################################################################################################ +# Do tests + +- name: Copy content without mode + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + register: result + ignore_errors: true + +- name: Check results + assert: + that: + - result is failed + - |- + result.msg in [ + "missing parameter(s) required by 'content': mode", + ] + +######################### Copy + +- name: Copy content (check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + check_mode: true + diff: false + register: result_1 + +- name: Copy content (check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + check_mode: true + diff: true + register: result_1_diff + +- name: Copy content (check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + register: result_2 + +- name: Copy content (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + check_mode: true + diff: false + register: result_3 + +- name: Copy content (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy content (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + register: result_4 + +- name: Copy content (idempotent, check mode, base 64) + docker_container_copy_into: + container: '{{ cname }}' + content: "{{ 'Content 1\n' | b64encode }}" + content_is_b64: true + container_path: '/file' + mode: 0644 + check_mode: true + diff: false + register: result_3b64 + +- name: Copy content (idempotent, check mode, base 64, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: "{{ 'Content 1\n' | b64encode }}" + content_is_b64: true + container_path: '/file' + mode: 0644 + check_mode: true + diff: true + register: result_3b64_diff + +- name: Copy content (idempotent, base 64) + docker_container_copy_into: + container: '{{ cname }}' + content: "{{ 'Content 1\n' | b64encode }}" + content_is_b64: true + container_path: '/file' + mode: 0644 + register: result_4b64 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_5 + +- name: Copy content (force, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + force: true + check_mode: true + diff: false + register: result_6 + +- name: Copy content (force, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + force: true + check_mode: true + diff: true + register: result_6_diff + +- name: Copy content (force) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0644 + force: true + register: result_7 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_8 + +- name: Copy content (force=false, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Some other content + container_path: '/file' + mode: 0777 + owner_id: 123 + group_id: 321 + force: false + check_mode: true + diff: false + register: result_9 + +- name: Copy content (force=false, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Some other content + container_path: '/file' + mode: 0777 + owner_id: 123 + group_id: 321 + force: false + check_mode: true + diff: true + register: result_9_diff + +- name: Copy content (force=false) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Some other content + container_path: '/file' + mode: 0777 + owner_id: 123 + group_id: 321 + force: false + register: result_10 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_11 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == '' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == 'dynamically generated' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 1\n' + - result_3_diff.diff.before_header == '/file' + - result_3_diff.diff.after == 'Content 1\n' + - result_3_diff.diff.after_header == 'dynamically generated' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_3b64 is not changed + - "'diff' not in result_3b64" + - result_3b64_diff.diff.before == 'Content 1\n' + - result_3b64_diff.diff.before_header == '/file' + - result_3b64_diff.diff.after == 'Content 1\n' + - result_3b64_diff.diff.after_header == 'dynamically generated' + - result_3b64 == (result_3b64_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4b64 is not changed + - result_5.stdout | b64decode == 'Content 1\n' + - result_5.stderr == '10 644 regular file 0 0 /file' + - result_6 is changed + - "'diff' not in result_6" + - result_6_diff.diff.before == 'Content 1\n' + - result_6_diff.diff.before_header == '/file' + - result_6_diff.diff.after == 'Content 1\n' + - result_6_diff.diff.after_header == 'dynamically generated' + - result_6 == (result_6_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_7 is changed + - result_8.stdout | b64decode == 'Content 1\n' + - result_8.stderr == '10 644 regular file 0 0 /file' + - result_9 is not changed + - "'diff' not in result_9" + - result_9_diff.diff.before == 'Content 1\n' + - result_9_diff.diff.before_header == '/file' + - result_9_diff.diff.after == 'Content 1\n' # note that force=false + - result_9_diff.diff.after_header == '/file' # note that force=false + - result_9 == (result_9_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_10 is not changed + - result_11.stdout | b64decode == 'Content 1\n' + - result_11.stderr == '10 644 regular file 0 0 /file' + +######################### Follow link - idempotence + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr; + chdir: /root + register: result_0 + +- name: Copy content following link (check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: true + check_mode: true + diff: false + register: result_1 + +- name: Copy content following link (check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: true + check_mode: true + diff: true + register: result_1_diff + +- name: Copy content following link + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: true + register: result_2 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_3 + +- name: Copy content following link (force, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: true + force: true + check_mode: true + diff: false + register: result_4 + +- name: Copy content following link (force, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: true + force: true + check_mode: true + diff: true + register: result_4_diff + +- name: Copy content following link (force) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: true + force: true + register: result_5 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_6 + +- name: Check results + assert: + that: + - result_0.stdout | b64decode == 'Content 1\n' + - result_0.stderr == "4 777 symbolic link 0 0 '/lnk' -> 'file'" + - result_1 is not changed + - result_1.container_path == '/file' + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 1\n' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == 'dynamically generated' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is not changed + - result_2.container_path == '/file' + - result_3.stdout | b64decode == 'Content 1\n' + - result_3.stderr_lines[0] == "4 777 symbolic link 0 0 '/lnk' -> 'file'" + - result_3.stderr_lines[1] == '10 644 regular file 0 0 /file' + - result_4 is changed + - result_4.container_path == '/file' + - "'diff' not in result_4" + - result_4_diff.diff.before == 'Content 1\n' + - result_4_diff.diff.before_header == '/file' + - result_4_diff.diff.after == 'Content 1\n' + - result_4_diff.diff.after_header == 'dynamically generated' + - result_4 == (result_4_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_5 is changed + - result_5.container_path == '/file' + - result_6.stdout | b64decode == 'Content 1\n' + - result_6.stderr_lines[0] == "4 777 symbolic link 0 0 '/lnk' -> 'file'" + - result_6.stderr_lines[1] == '10 644 regular file 0 0 /file' + +######################### Do not follow link - replace by file + +- name: Copy content not following link (check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: false + check_mode: true + diff: false + register: result_1 + +- name: Copy content not following link (check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: false + check_mode: true + diff: true + register: result_1_diff + +- name: Copy content not following link + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + follow: false + register: result_2 + +- name: Copy content not following link (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + check_mode: true + diff: false + register: result_3 + +- name: Copy content not following link (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy content not following link (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr + chdir: /root + register: result_5 + +- name: Copy content not following link (force, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + force: true + check_mode: true + diff: false + register: result_6 + +- name: Copy content not following link (force, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + force: true + check_mode: true + diff: true + register: result_6_diff + +- name: Copy content not following link (force) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/lnk' + mode: 0644 + force: true + register: result_7 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr + chdir: /root + register: result_8 + +- name: Check results + assert: + that: + - result_1 is changed + - result_1.container_path == '/lnk' + - "'diff' not in result_1" + - result_1_diff.diff.before == '/file' + - result_1_diff.diff.before_header == '/lnk' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == 'dynamically generated' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_2.container_path == '/lnk' + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 1\n' + - result_3_diff.diff.before_header == '/lnk' + - result_3_diff.diff.after == 'Content 1\n' + - result_3_diff.diff.after_header == 'dynamically generated' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 1\n' + - result_5.stderr == '10 644 regular file 0 0 /lnk' + - result_6 is changed + - result_6.container_path == '/lnk' + - "'diff' not in result_6" + - result_6_diff.diff.before == 'Content 1\n' + - result_6_diff.diff.before_header == '/lnk' + - result_6_diff.diff.after == 'Content 1\n' + - result_6_diff.diff.after_header == 'dynamically generated' + - result_6 == (result_6_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_7 is changed + - result_7.container_path == '/lnk' + - result_8.stdout | b64decode == 'Content 1\n' + - result_8.stderr == '10 644 regular file 0 0 /lnk' + +######################### Replace directory by file + +- name: Copy content to replace directory (check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/dir' + mode: 0644 + follow: false + check_mode: true + diff: false + register: result_1 + +- name: Copy content to replace directory (check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/dir' + mode: 0644 + follow: false + check_mode: true + diff: true + register: result_1_diff + +- name: Copy content to replace directory (check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/dir' + mode: 0644 + follow: false + register: result_2 + +- name: Copy content to replace directory (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/dir' + mode: 0644 + check_mode: true + diff: false + register: result_3 + +- name: Copy content to replace directory (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/dir' + mode: 0644 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy content to replace directory (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/dir' + mode: 0644 + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /dir | base64; + stat -c '%s %a %F %u %g %N' /dir > /dev/stderr + chdir: /root + register: result_5 + +- name: Check results + assert: + that: + - result_1 is changed + - result_1.container_path == '/dir' + - "'diff' not in result_1" + - result_1_diff.diff.before == '(directory)' + - result_1_diff.diff.before_header == '/dir' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == 'dynamically generated' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_2.container_path == '/dir' + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 1\n' + - result_3_diff.diff.before_header == '/dir' + - result_3_diff.diff.after == 'Content 1\n' + - result_3_diff.diff.after_header == 'dynamically generated' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 1\n' + - result_5.stderr == '10 644 regular file 0 0 /dir' + +######################### Modify + +- name: Copy content (changed, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0644 + check_mode: true + diff: false + register: result_1 + +- name: Copy content (changed, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0644 + check_mode: true + diff: true + register: result_1_diff + +- name: Copy content (changed) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0644 + register: result_2 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_3 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 1\n' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 2\nExtra line' + - result_1_diff.diff.after_header == 'dynamically generated' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3.stdout | b64decode == 'Content 2\nExtra line' + - result_3.stderr == '20 644 regular file 0 0 /file' + +######################### Change mode + +- name: Copy content (mode changed, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + check_mode: true + diff: false + register: result_1 + +- name: Copy content (mode changed, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + check_mode: true + diff: true + register: result_1_diff + +- name: Copy content (mode changed) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + register: result_2 + +- name: Copy content (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + check_mode: true + diff: false + register: result_3 + +- name: Copy content (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy content (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_5 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 2\nExtra line' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 2\nExtra line' + - result_1_diff.diff.after_header == 'dynamically generated' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 2\nExtra line' + - result_3_diff.diff.before_header == '/file' + - result_3_diff.diff.after == 'Content 2\nExtra line' + - result_3_diff.diff.after_header == 'dynamically generated' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 2\nExtra line' + - result_5.stderr == '20 707 regular file 0 0 /file' + +######################### Change owner and group + +- name: Copy content (owner/group changed, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: false + register: result_1 + +- name: Copy content (owner/group changed, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: true + register: result_1_diff + +- name: Copy content (owner/group changed) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + register: result_2 + +- name: Copy content (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: false + register: result_3 + +- name: Copy content (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy content (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_5 + +- name: Copy content (owner/group changed again, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 13 + group_id: 13 + check_mode: true + diff: false + register: result_6 + +- name: Copy content (owner/group changed again, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 13 + group_id: 13 + check_mode: true + diff: true + register: result_6_diff + +- name: Copy content (owner/group changed again) + docker_container_copy_into: + container: '{{ cname }}' + content: |- + Content 2 + Extra line + container_path: '/file' + mode: 0707 + owner_id: 13 + group_id: 13 + register: result_7 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_8 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 2\nExtra line' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 2\nExtra line' + - result_1_diff.diff.after_header == 'dynamically generated' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 2\nExtra line' + - result_3_diff.diff.before_header == '/file' + - result_3_diff.diff.after == 'Content 2\nExtra line' + - result_3_diff.diff.after_header == 'dynamically generated' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 2\nExtra line' + - result_5.stderr == '20 707 regular file 12 910 /file' + - result_6 is changed + - "'diff' not in result_6" + - result_6_diff.diff.before == 'Content 2\nExtra line' + - result_6_diff.diff.before_header == '/file' + - result_6_diff.diff.after == 'Content 2\nExtra line' + - result_6_diff.diff.after_header == 'dynamically generated' + - result_6 == (result_6_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_7 is changed + - result_8.stdout | b64decode == 'Content 2\nExtra line' + - result_8.stderr == '20 707 regular file 13 13 /file' + +######################### Operate with stopped container + +- name: Stop container + docker_container: + name: "{{ cname }}" + state: stopped + stop_timeout: 1 + +- name: Copy content (stopped container, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: false + register: result_1 + +- name: Copy content (stopped container, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: true + register: result_1_diff + +- name: Copy content (stopped container) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + register: result_2 + +- name: Copy content (stopped container, idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: false + register: result_3 + +- name: Copy content (stopped container, idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy content (stopped container, idempotent) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + register: result_4 + +- name: Copy content (stopped container, no owner/group provided, should fail) + docker_container_copy_into: + container: '{{ cname }}' + content: | + Content 1 + container_path: '/file' + mode: 0707 + register: result_5 + ignore_errors: true + +- name: Start container + docker_container: + name: "{{ cname }}" + state: started + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_6 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 2\nExtra line' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == 'dynamically generated' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 1\n' + - result_3_diff.diff.before_header == '/file' + - result_3_diff.diff.after == 'Content 1\n' + - result_3_diff.diff.after_header == 'dynamically generated' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5 is failed + - result_5.msg == ('Cannot execute command in paused container "' ~ cname ~ '"') + - result_6.stdout | b64decode == 'Content 1\n' + - result_6.stderr == '10 707 regular file 12 910 /file' + +################################################################################################ +# Cleanup + +- name: Remove container + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/tests/file.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/tests/file.yml new file mode 100644 index 000000000..5431ae359 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_copy_into/tasks/tests/file.yml @@ -0,0 +1,1065 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + cname: "{{ cname_prefix ~ '-f' }}" +- name: Registering container name + set_fact: + cnames: "{{ cnames + [cname] }}" + +# Create container + +- name: Create container + docker_container: + image: "{{ docker_test_image_alpine }}" + command: + - /bin/sh + - "-c" + - >- + mkdir /dir; + ln -s file /lnk; + ln -s lnk3 /lnk2; + ln -s lnk2 /lnk1; + sleep 10m; + name: "{{ cname }}" + state: started + +# Create files + +- name: Create file 1 + copy: + dest: '{{ remote_tmp_dir }}/file_1' + content: | + Content 1 + mode: 0644 + +- name: Create file 2 + copy: + dest: '{{ remote_tmp_dir }}/file_2' + content: |- + Content 2 + Extra line + mode: 0644 + +- name: Create link 1 + file: + dest: '{{ remote_tmp_dir }}/link_1' + state: link + src: file_1 + follow: false + mode: 0644 + +- name: Create link 2 + file: + dest: '{{ remote_tmp_dir }}/link_2' + state: link + src: dead + force: true + follow: false + mode: 0644 + +################################################################################################ +# Do tests + +######################### Copy + +- name: Copy file (check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + check_mode: true + diff: false + register: result_1 + +- name: Copy file (check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + check_mode: true + diff: true + register: result_1_diff + +- name: Copy file (check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + register: result_2 + +- name: Copy file (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + check_mode: true + diff: false + register: result_3 + +- name: Copy file (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + check_mode: true + diff: true + register: result_3_diff + +- name: Copy file (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_5 + +- name: Copy file (force, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + force: true + check_mode: true + diff: false + register: result_6 + +- name: Copy file (force, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + force: true + check_mode: true + diff: true + register: result_6_diff + +- name: Copy file (force) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + force: true + register: result_7 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_8 + +- name: Copy file (force=false, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0777 + owner_id: 123 + group_id: 321 + force: false + check_mode: true + diff: false + register: result_9 + +- name: Copy file (force=false, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0777 + owner_id: 123 + group_id: 321 + force: false + check_mode: true + diff: true + register: result_9_diff + +- name: Copy file (force=false) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0777 + owner_id: 123 + group_id: 321 + force: false + register: result_10 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_11 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == '' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 1\n' + - result_3_diff.diff.before_header == '/file' + - result_3_diff.diff.after == 'Content 1\n' + - result_3_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 1\n' + - result_5.stderr == '10 644 regular file 0 0 /file' + - result_6 is changed + - "'diff' not in result_6" + - result_6_diff.diff.before == 'Content 1\n' + - result_6_diff.diff.before_header == '/file' + - result_6_diff.diff.after == 'Content 1\n' + - result_6_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_6 == (result_6_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_7 is changed + - result_8.stdout | b64decode == 'Content 1\n' + - result_8.stderr == '10 644 regular file 0 0 /file' + - result_9 is not changed + - "'diff' not in result_9" + - result_9_diff.diff.before == 'Content 1\n' + - result_9_diff.diff.before_header == '/file' + - result_9_diff.diff.after == 'Content 1\n' # note that force=false + - result_9_diff.diff.after_header == '/file' # note that force=false + - result_9 == (result_9_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_10 is not changed + - result_11.stdout | b64decode == 'Content 1\n' + - result_11.stderr == '10 644 regular file 0 0 /file' + +######################### Follow link - idempotence + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr; + chdir: /root + register: result_0 + +- name: Copy file following link (check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: true + check_mode: true + diff: false + register: result_1 + +- name: Copy file following link (check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: true + check_mode: true + diff: true + register: result_1_diff + +- name: Copy file following link + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: true + register: result_2 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_3 + +- name: Copy file following link (force, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: true + force: true + check_mode: true + diff: false + register: result_4 + +- name: Copy file following link (force, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: true + force: true + check_mode: true + diff: true + register: result_4_diff + +- name: Copy file following link (force) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: true + force: true + register: result_5 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_6 + +- name: Check results + assert: + that: + - result_0.stdout | b64decode == 'Content 1\n' + - result_0.stderr == "4 777 symbolic link 0 0 '/lnk' -> 'file'" + - result_1 is not changed + - result_1.container_path == '/file' + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 1\n' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is not changed + - result_2.container_path == '/file' + - result_3.stdout | b64decode == 'Content 1\n' + - result_3.stderr_lines[0] == "4 777 symbolic link 0 0 '/lnk' -> 'file'" + - result_3.stderr_lines[1] == '10 644 regular file 0 0 /file' + - result_4 is changed + - result_4.container_path == '/file' + - "'diff' not in result_4" + - result_4_diff.diff.before == 'Content 1\n' + - result_4_diff.diff.before_header == '/file' + - result_4_diff.diff.after == 'Content 1\n' + - result_4_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_4 == (result_4_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_5 is changed + - result_5.container_path == '/file' + - result_6.stdout | b64decode == 'Content 1\n' + - result_6.stderr_lines[0] == "4 777 symbolic link 0 0 '/lnk' -> 'file'" + - result_6.stderr_lines[1] == '10 644 regular file 0 0 /file' + +######################### Do not follow link - replace by file + +- name: Copy file not following link (check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: false + check_mode: true + diff: false + register: result_1 + +- name: Copy file not following link (check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: false + check_mode: true + diff: true + register: result_1_diff + +- name: Copy file not following link + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + follow: false + register: result_2 + +- name: Copy file not following link (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + check_mode: true + diff: false + register: result_3 + +- name: Copy file not following link (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + check_mode: true + diff: true + register: result_3_diff + +- name: Copy file not following link (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr + chdir: /root + register: result_5 + +- name: Copy file not following link (force, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + force: true + check_mode: true + diff: false + register: result_6 + +- name: Copy file not following link (force, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + force: true + check_mode: true + diff: true + register: result_6_diff + +- name: Copy file not following link (force) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/lnk' + force: true + register: result_7 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /lnk | base64; + stat -c '%s %a %F %u %g %N' /lnk > /dev/stderr + chdir: /root + register: result_8 + +- name: Check results + assert: + that: + - result_1 is changed + - result_1.container_path == '/lnk' + - "'diff' not in result_1" + - result_1_diff.diff.before == '/file' + - result_1_diff.diff.before_header == '/lnk' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_2.container_path == '/lnk' + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 1\n' + - result_3_diff.diff.before_header == '/lnk' + - result_3_diff.diff.after == 'Content 1\n' + - result_3_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 1\n' + - result_5.stderr == '10 644 regular file 0 0 /lnk' + - result_6 is changed + - result_6.container_path == '/lnk' + - "'diff' not in result_6" + - result_6_diff.diff.before == 'Content 1\n' + - result_6_diff.diff.before_header == '/lnk' + - result_6_diff.diff.after == 'Content 1\n' + - result_6_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_6 == (result_6_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_7 is changed + - result_7.container_path == '/lnk' + - result_8.stdout | b64decode == 'Content 1\n' + - result_8.stderr == '10 644 regular file 0 0 /lnk' + +######################### Replace directory by file + +- name: Copy file to replace directory (check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/dir' + follow: false + check_mode: true + diff: false + register: result_1 + +- name: Copy file to replace directory (check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/dir' + follow: false + check_mode: true + diff: true + register: result_1_diff + +- name: Copy file to replace directory (check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/dir' + follow: false + register: result_2 + +- name: Copy file to replace directory (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/dir' + check_mode: true + diff: false + register: result_3 + +- name: Copy file to replace directory (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/dir' + check_mode: true + diff: true + register: result_3_diff + +- name: Copy file to replace directory (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/dir' + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /dir | base64; + stat -c '%s %a %F %u %g %N' /dir > /dev/stderr + chdir: /root + register: result_5 + +- name: Check results + assert: + that: + - result_1 is changed + - result_1.container_path == '/dir' + - "'diff' not in result_1" + - result_1_diff.diff.before == '(directory)' + - result_1_diff.diff.before_header == '/dir' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_2.container_path == '/dir' + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 1\n' + - result_3_diff.diff.before_header == '/dir' + - result_3_diff.diff.after == 'Content 1\n' + - result_3_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 1\n' + - result_5.stderr == '10 644 regular file 0 0 /dir' + +######################### Modify + +- name: Copy file (changed, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + check_mode: true + diff: false + register: result_1 + +- name: Copy file (changed, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + check_mode: true + diff: true + register: result_1_diff + +- name: Copy file (changed) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + register: result_2 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_3 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 1\n' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 2\nExtra line' + - result_1_diff.diff.after_header == remote_tmp_dir ~ '/file_2' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3.stdout | b64decode == 'Content 2\nExtra line' + - result_3.stderr == '20 644 regular file 0 0 /file' + +######################### Change mode + +- name: Copy file (mode changed, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + check_mode: true + diff: false + register: result_1 + +- name: Copy file (mode changed, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + check_mode: true + diff: true + register: result_1_diff + +- name: Copy file (mode changed) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + register: result_2 + +- name: Copy file (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + check_mode: true + diff: false + register: result_3 + +- name: Copy file (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy file (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_5 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 2\nExtra line' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 2\nExtra line' + - result_1_diff.diff.after_header == remote_tmp_dir ~ '/file_2' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 2\nExtra line' + - result_3_diff.diff.before_header == '/file' + - result_3_diff.diff.after == 'Content 2\nExtra line' + - result_3_diff.diff.after_header == remote_tmp_dir ~ '/file_2' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 2\nExtra line' + - result_5.stderr == '20 707 regular file 0 0 /file' + +######################### Change owner and group + +- name: Copy file (owner/group changed, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: false + register: result_1 + +- name: Copy file (owner/group changed, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: true + register: result_1_diff + +- name: Copy file (owner/group changed) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + register: result_2 + +- name: Copy file (idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: false + register: result_3 + +- name: Copy file (idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy file (idempotent) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + register: result_4 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_5 + +- name: Copy file (owner/group changed again, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 13 + group_id: 13 + check_mode: true + diff: false + register: result_6 + +- name: Copy file (owner/group changed again, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 13 + group_id: 13 + check_mode: true + diff: true + register: result_6_diff + +- name: Copy file (owner/group changed again) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_2' + container_path: '/file' + mode: 0707 + owner_id: 13 + group_id: 13 + register: result_7 + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_8 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 2\nExtra line' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 2\nExtra line' + - result_1_diff.diff.after_header == remote_tmp_dir ~ '/file_2' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 2\nExtra line' + - result_3_diff.diff.before_header == '/file' + - result_3_diff.diff.after == 'Content 2\nExtra line' + - result_3_diff.diff.after_header == remote_tmp_dir ~ '/file_2' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5.stdout | b64decode == 'Content 2\nExtra line' + - result_5.stderr == '20 707 regular file 12 910 /file' + - result_6 is changed + - "'diff' not in result_6" + - result_6_diff.diff.before == 'Content 2\nExtra line' + - result_6_diff.diff.before_header == '/file' + - result_6_diff.diff.after == 'Content 2\nExtra line' + - result_6_diff.diff.after_header == remote_tmp_dir ~ '/file_2' + - result_6 == (result_6_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_7 is changed + - result_8.stdout | b64decode == 'Content 2\nExtra line' + - result_8.stderr == '20 707 regular file 13 13 /file' + +######################### Operate with stopped container + +- name: Stop container + docker_container: + name: "{{ cname }}" + state: stopped + stop_timeout: 1 + +- name: Copy file (stopped container, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: false + register: result_1 + +- name: Copy file (stopped container, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: true + register: result_1_diff + +- name: Copy file (stopped container) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + register: result_2 + +- name: Copy file (stopped container, idempotent, check mode) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: false + register: result_3 + +- name: Copy file (stopped container, idempotent, check mode, diff) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + check_mode: true + diff: true + register: result_3_diff + +- name: Copy file (stopped container, idempotent) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + mode: 0707 + owner_id: 12 + group_id: 910 + register: result_4 + +- name: Copy file (stopped container, no owner/group provided, should fail) + docker_container_copy_into: + container: '{{ cname }}' + path: '{{ remote_tmp_dir }}/file_1' + container_path: '/file' + mode: 0707 + register: result_5 + ignore_errors: true + +- name: Start container + docker_container: + name: "{{ cname }}" + state: started + +- name: Dump file + docker_container_exec: + container: '{{ cname }}' + argv: + - /bin/sh + - "-c" + - >- + cat /file | base64; + stat -c '%s %a %F %u %g %N' /file > /dev/stderr + chdir: /root + register: result_6 + +- name: Check results + assert: + that: + - result_1 is changed + - "'diff' not in result_1" + - result_1_diff.diff.before == 'Content 2\nExtra line' + - result_1_diff.diff.before_header == '/file' + - result_1_diff.diff.after == 'Content 1\n' + - result_1_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_1 == (result_1_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_2 is changed + - result_3 is not changed + - "'diff' not in result_3" + - result_3_diff.diff.before == 'Content 1\n' + - result_3_diff.diff.before_header == '/file' + - result_3_diff.diff.after == 'Content 1\n' + - result_3_diff.diff.after_header == remote_tmp_dir ~ '/file_1' + - result_3 == (result_3_diff | dict2items | rejectattr('key', 'eq', 'diff') | items2dict) + - result_4 is not changed + - result_5 is failed + - result_5.msg == ('Cannot execute command in paused container "' ~ cname ~ '"') + - result_6.stdout | b64decode == 'Content 1\n' + - result_6.stderr == '10 707 regular file 12 910 /file' + +################################################################################################ +# Cleanup + +- name: Remove container + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/tasks/main.yml new file mode 100644 index 000000000..61c3b81ef --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_exec/tasks/main.yml @@ -0,0 +1,228 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Create random container name + set_fact: + cname: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + + - name: Make sure container is not there + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + + - name: Execute in a non-present container + docker_container_exec: + container: "{{ cname }}" + command: "/bin/bash -c 'ls -a'" + register: result + ignore_errors: true + + - assert: + that: + - result is failed + - "'Could not find container' in result.msg" + + - name: Make sure container exists + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + force_kill: true + + - name: Execute in a present container (command) + docker_container_exec: + container: "{{ cname }}" + command: "/bin/sh -c 'ls -a'" + register: result_cmd + + - assert: + that: + - result_cmd.rc == 0 + - "'stdout' in result_cmd" + - "'stdout_lines' in result_cmd" + - "'stderr' in result_cmd" + - "'stderr_lines' in result_cmd" + + - name: Execute in a present container (argv) + docker_container_exec: + container: "{{ cname }}" + argv: + - /bin/sh + - '-c' + - ls -a + register: result_argv + + - assert: + that: + - result_argv.rc == 0 + - "'stdout' in result_argv" + - "'stdout_lines' in result_argv" + - "'stderr' in result_argv" + - "'stderr_lines' in result_argv" + - result_cmd.stdout == result_argv.stdout + + - name: Execute in a present container (cat without stdin) + docker_container_exec: + container: "{{ cname }}" + argv: + - /bin/sh + - '-c' + - cat + register: result + + - assert: + that: + - result.rc == 0 + - result.stdout == '' + - result.stdout_lines == [] + - result.stderr == '' + - result.stderr_lines == [] + + - name: Execute in a present container (cat with stdin) + docker_container_exec: + container: "{{ cname }}" + argv: + - /bin/sh + - '-c' + - cat + stdin: Hello world! + strip_empty_ends: false + register: result + + - assert: + that: + - result.rc == 0 + - result.stdout == 'Hello world!\n' + - result.stdout_lines == ['Hello world!'] + - result.stderr == '' + - result.stderr_lines == [] + + - name: Execute in a present container (cat with stdin, no newline) + docker_container_exec: + container: "{{ cname }}" + argv: + - /bin/sh + - '-c' + - cat + stdin: Hello world! + stdin_add_newline: false + strip_empty_ends: false + register: result + + - assert: + that: + - result.rc == 0 + - result.stdout == 'Hello world!' + - result.stdout_lines == ['Hello world!'] + - result.stderr == '' + - result.stderr_lines == [] + + - name: Execute in a present container (cat with stdin, newline but stripping) + docker_container_exec: + container: "{{ cname }}" + argv: + - /bin/sh + - '-c' + - cat + stdin: Hello world! + stdin_add_newline: true + strip_empty_ends: true + register: result + + - assert: + that: + - result.rc == 0 + - result.stdout == 'Hello world!' + - result.stdout_lines == ['Hello world!'] + - result.stderr == '' + - result.stderr_lines == [] + + - name: Prepare long string + set_fact: + very_long_string: "{{ 'something long ' * 10000 }}" + very_long_string2: "{{ 'something else ' * 5000 }}" + + - name: Execute in a present container (long stdin) + docker_container_exec: + container: "{{ cname }}" + argv: + - /bin/sh + - '-c' + - cat + stdin: |- + {{ very_long_string }} + {{ very_long_string2 }} + register: result + + - assert: + that: + - result is changed + - result.rc == 0 + - result.stdout == very_long_string ~ '\n' ~ very_long_string2 + - result.stdout_lines == [very_long_string, very_long_string2] + - result.stderr == '' + - result.stderr_lines == [] + - "'exec_id' not in result" + + - name: Execute in a present container (detached) + docker_container_exec: + container: "{{ cname }}" + argv: + - /bin/sh + - '-c' + - sleep 1m + detach: true + register: result + + - debug: var=result + + - assert: + that: + - result is changed + - "'rc' not in result" + - "'stdout' not in result" + - "'stderr' not in result" + - result.exec_id is string + + - name: Execute in a present container (environment variable) + docker_container_exec: + container: "{{ cname }}" + argv: + - /bin/sh + - '-c' + - 'echo "$FOO" ; echo $FOO > /dev/stderr' + env: + FOO: |- + bar + baz + register: result + + - assert: + that: + - result.rc == 0 + - result.stdout == 'bar\nbaz' + - result.stdout_lines == ['bar', 'baz'] + - result.stderr == 'bar baz' + - result.stderr_lines == ['bar baz'] + + always: + - name: Cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_container_exec tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_container_info/aliases new file mode 100644 index 000000000..0837c7405 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/5 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_info/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_info/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_container_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_container_info/tasks/main.yml new file mode 100644 index 000000000..2df597eb3 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_container_info/tasks/main.yml @@ -0,0 +1,84 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Create random container name + set_fact: + cname: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + + - name: Make sure container is not there + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + + - name: Inspect a non-present container + docker_container_info: + name: "{{ cname }}" + register: result + + - assert: + that: + - "not result.exists" + - "'container' in result" + - "result.container is none" + + - name: Make sure container exists + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + state: started + force_kill: true + + - name: Inspect a present container + docker_container_info: + name: "{{ cname }}" + register: result + - name: Dump docker_container_info result + debug: var=result + + - name: "Comparison: use 'docker inspect'" + command: docker inspect "{{ cname }}" + register: docker_inspect + ignore_errors: true + - block: + - set_fact: + docker_inspect_result: "{{ docker_inspect.stdout | from_json }}" + - name: Dump docker inspect result + debug: var=docker_inspect_result + when: docker_inspect is not failed + + - assert: + that: + - result.exists + - "'container' in result" + - "result.container" + + - assert: + that: + - "result.container == docker_inspect_result[0]" + when: docker_inspect is not failed + - assert: + that: + - "'is too new. Maximum supported API version is' in docker_inspect.stderr" + when: docker_inspect is failed + + always: + - name: Cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: true + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_container_info tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_host_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_host_info/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_host_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_host_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_host_info/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_host_info/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_host_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_host_info/tasks/main.yml new file mode 100644 index 000000000..e26790f3b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_host_info/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_host_info.yml + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_host_info tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_host_info/tasks/test_host_info.yml b/ansible_collections/community/docker/tests/integration/targets/docker_host_info/tasks/test_host_info.yml new file mode 100644 index 000000000..0d090db97 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_host_info/tasks/test_host_info.yml @@ -0,0 +1,364 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create random container/volume name + set_fact: + cname: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + cname2: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + vname: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + +- debug: + msg: "Using container names '{{ cname }}' and '{{ cname2 }}', and volume name '{{ vname }}'" + +- block: + - name: Get info on Docker host + docker_host_info: + register: output + + - name: assert reading docker host facts when docker is running + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + +# Container and volume are created so that all lists are non-empty: +# * container and volume lists are non-emtpy because of the created objects; +# * image list is non-empty because the image of the container is there; +# * network list is always non-empty (default networks). + - name: Create running container + docker_container: + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + labels: + key1: value1 + key2: value2 + state: started + register: container_output + + - name: Create running container + docker_container: + image: "{{ docker_test_image_alpine }}" + name: "{{ cname2 }}" + labels: + key2: value2 + key3: value3 + state: stopped + register: container2_output + + - assert: + that: + - container_output is changed + - container2_output is changed + + - name: Create a volume + docker_volume: + name: "{{ vname }}" + register: volume_output + + - assert: + that: + - volume_output is changed + + - name: Get info on Docker host and list containers + docker_host_info: + containers: true + register: output + + - name: assert reading docker host facts when docker is running and list containers + assert: + that: + - 'output.host_info.Name is string' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + - 'output.containers[0].Image is string' + - 'output.containers[0].ImageID is not defined' + + - name: Get info on Docker host and list containers matching filters (single label) + docker_host_info: + containers: true + containers_filters: + label: key1=value1 + register: output + + - name: assert container is returned when filters are matched (single label) + assert: + that: "{{ output.containers | length }} == 1" + + - name: Get info on Docker host and list containers matching filters (multiple labels) + docker_host_info: + containers: true + containers_filters: + label: + - key1=value1 + - key2=value2 + register: output + + - name: assert container is returned when filters are matched (multiple labels) + assert: + that: "{{ output.containers | length }} == 1" + + - name: Get info on Docker host and do not list containers which do not match filters + docker_host_info: + containers: true + containers_filters: + label: + - key1=value1 + - key2=value2 + - key3=value3 + register: output + + - name: assert no container is returned when filters are not matched + assert: + that: "{{ output.containers | length }} == 0" + + - name: Get info on Docker host and list containers matching filters (single label, not all containers) + docker_host_info: + containers: true + containers_all: false + containers_filters: + label: key2=value2 + register: output + + - name: Get info on Docker host and list containers matching filters (single label, all containers) + docker_host_info: + containers: true + containers_all: true + containers_filters: + label: key2=value2 + register: output_all + + - name: assert one resp. two container is returned + assert: + that: + - "{{ output.containers | length }} == 1" + - "{{ output_all.containers | length }} == 2" + + - name: Get info on Docker host and list containers with verbose output + docker_host_info: + containers: true + verbose_output: true + register: output + + - name: assert reading docker host facts when docker is running and list containers with verbose output + assert: + that: + - 'output.host_info.Name is string' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + - 'output.containers[0].Image is string' + - 'output.containers[0].ImageID is string' + + - name: Get info on Docker host and list images + docker_host_info: + images: true + register: output + + - name: assert reading docker host facts when docker is running and list images + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images[0].Id is string' + - 'output.images[0].ParentId is not defined' + - 'output.disk_usage is not defined' + + - name: Get info on Docker host and list images with verbose output + docker_host_info: + images: true + verbose_output: true + register: output + + - name: assert reading docker host facts when docker is running and list images with verbose output + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images[0].Id is string' + - 'output.images[0].ParentId is string' + - 'output.disk_usage is not defined' + + - name: Get info on Docker host and list networks + docker_host_info: + networks: true + register: output + + - name: assert reading docker host facts when docker is running and list networks + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks[0].Id is string' + - 'output.networks[0].Created is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + + - name: Get info on Docker host and list networks with verbose output + docker_host_info: + networks: true + verbose_output: true + register: output + + - name: assert reading docker host facts when docker is running and list networks with verbose output + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks[0].Id is string' + - 'output.networks[0].Created is string' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + + - name: Get info on Docker host and list volumes + docker_host_info: + volumes: true + register: output + + - name: assert reading docker host facts when docker is running and list volumes + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes[0].Name is string' + - 'output.volumes[0].Mountpoint is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + + - name: Get info on Docker host and list volumes with verbose output + docker_host_info: + volumes: true + verbose_output: true + register: output + + - name: assert reading docker host facts when docker is running and list volumes with verbose output + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes[0].Name is string' + - 'output.volumes[0].Mountpoint is string' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + + - name: Get info on Docker host and get disk usage + docker_host_info: + disk_usage: true + register: output + + - name: assert reading docker host facts when docker is running and get disk usage + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage.LayersSize is number' + - 'output.disk_usage.Images is not defined' + - 'output.disk_usage.Containers is not defined' + - 'output.disk_usage.Volumes is not defined' + + - name: Get info on Docker host and get disk usage with verbose output + docker_host_info: + disk_usage: true + verbose_output: true + register: output + + - name: assert reading docker host facts when docker is running and get disk usage with verbose output + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage.LayersSize is number' + - 'output.disk_usage.Images is sequence' + - 'output.disk_usage.Containers is sequence' + - 'output.disk_usage.Volumes is sequence' + + - name: Get info on Docker host, disk usage and get all lists together + docker_host_info: + volumes: true + containers: true + networks: true + images: true + disk_usage: true + register: output + + - name: assert reading docker host facts when docker is running, disk usage and get lists together + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers[0].Image is string' + - 'output.containers[0].ImageID is not defined' + - 'output.networks[0].Id is string' + - 'output.networks[0].Created is not defined' + - 'output.volumes[0].Name is string' + - 'output.volumes[0].Mountpoint is not defined' + - 'output.images[0].Id is string' + - 'output.images[0].ParentId is not defined' + - 'output.disk_usage.LayersSize is number' + - 'output.disk_usage.Images is not defined' + - 'output.disk_usage.Containers is not defined' + - 'output.disk_usage.Volumes is not defined' + + - name: Get info on Docker host, disk usage and get all lists together with verbose output + docker_host_info: + volumes: true + containers: true + networks: true + images: true + disk_usage: true + verbose_output: true + register: output + + - name: assert reading docker host facts when docker is running and get disk usage with verbose output + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers[0].Image is string' + - 'output.containers[0].ImageID is string' + - 'output.networks[0].Id is string' + - 'output.networks[0].Created is string' + - 'output.volumes[0].Name is string' + - 'output.volumes[0].Mountpoint is string' + - 'output.images[0].Id is string' + - 'output.images[0].ParentId is string' + - 'output.disk_usage.LayersSize is number' + - 'output.disk_usage.Images is sequence' + - 'output.disk_usage.Containers is sequence' + - 'output.disk_usage.Volumes is sequence' + + always: + - name: Delete containers + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + loop: + - "{{ cname }}" + - "{{ cname2 }}" + + - name: Delete volume + docker_volume: + name: "{{ vname }}" + state: absent diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_image/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image/meta/main.yml new file mode 100644 index 000000000..f7ba9ab1b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker_registry + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/main.yml new file mode 100644 index 000000000..88b23cfe7 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/main.yml @@ -0,0 +1,13 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6'] + include_tasks: + file: test.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/test.yml new file mode 100644 index 000000000..50bb84ffc --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/test.yml @@ -0,0 +1,54 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create random name prefix + set_fact: + name_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" +- name: Create image and container list + set_fact: + inames: [] + cnames: [] + +- debug: + msg: "Using name prefix {{ name_prefix }}" + +- name: Create files directory + file: + path: '{{ remote_tmp_dir }}/files' + state: directory + +- name: Template files + template: + src: '{{ item }}' + dest: '{{ remote_tmp_dir }}/files/{{ item }}' + loop: + - ArgsDockerfile + - Dockerfile + - EtcHostsDockerfile + - MyDockerfile + - StagedDockerfile + +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: "Make sure all images are removed" + docker_image: + name: "{{ item }}" + state: absent + with_items: "{{ inames }}" + - name: "Make sure all containers are removed" + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + with_items: "{{ cnames }}" + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_image tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/basic.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/basic.yml new file mode 100644 index 000000000..78b4f7738 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/basic.yml @@ -0,0 +1,139 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +## basic ########################################################### +#################################################################### + +- name: Make sure image is not there + docker_image: + name: "{{ docker_test_image_hello_world }}" + state: absent + force_absent: true + register: absent_1 + +- name: Make sure image is not there (idempotency) + docker_image: + name: "{{ docker_test_image_hello_world }}" + state: absent + register: absent_2 + +- assert: + that: + - absent_2 is not changed + +- name: Make sure image is there + docker_image: + name: "{{ docker_test_image_hello_world }}" + state: present + source: pull + pull: + platform: amd64 + register: present_1 + +- name: Make sure image is there (idempotent) + docker_image: + name: "{{ docker_test_image_hello_world }}" + state: present + source: pull + pull: + platform: amd64 + register: present_2 + +- assert: + that: + - present_1 is changed + - present_2 is not changed + +- name: Make sure tag is not there + docker_image: + name: "{{ docker_test_image_hello_world_base }}:alias" + state: absent + +- name: Tag image with alias + docker_image: + source: local + name: "{{ docker_test_image_hello_world }}" + repository: "{{ docker_test_image_hello_world_base }}:alias" + register: tag_1 + +- name: Tag image with alias (idempotent) + docker_image: + source: local + name: "{{ docker_test_image_hello_world }}" + repository: "{{ docker_test_image_hello_world_base }}:alias" + register: tag_2 + +- name: Tag image with alias (force, still idempotent) + docker_image: + source: local + name: "{{ docker_test_image_hello_world }}" + repository: "{{ docker_test_image_hello_world_base }}:alias" + force_tag: true + register: tag_3 + +- name: Tag image with ID instead of name + docker_image: + source: local + name: "{{ present_1.image.Id }}" + repository: "{{ docker_test_image_hello_world_base }}:alias" + register: tag_4 + +- assert: + that: + - tag_1 is changed + - tag_2 is not changed + - tag_3 is not changed + - tag_4 is not changed + +- name: Cleanup alias tag + docker_image: + name: "{{ docker_test_image_hello_world_base }}:alias" + state: absent + +- name: Tag image with ID instead of name (use ID for repository, must fail) + docker_image: + source: local + name: "{{ docker_test_image_hello_world }}" + repository: "{{ present_1.image.Id }}" + register: fail_1 + ignore_errors: true + +- name: Push image with ID (must fail) + docker_image: + source: local + name: "{{ present_1.image.Id }}" + push: true + register: fail_2 + ignore_errors: true + +- name: Pull image ID (must fail) + docker_image: + source: pull + name: "{{ present_1.image.Id }}" + force_source: true + register: fail_3 + ignore_errors: true + +- name: buildargs + docker_image: + source: build + name: "{{ present_1.image.Id }}" + build: + path: "{{ remote_tmp_dir }}/files" + force_source: true + register: fail_4 + ignore_errors: true + +- assert: + that: + - fail_1 is failed + - "'`repository` must not be an image ID' in fail_1.msg" + - fail_2 is failed + - "'Cannot push an image by ID' in fail_2.msg" + - fail_3 is failed + - "'Image name must not be an image ID for source=pull' in fail_3.msg" + - fail_4 is failed + - "'Image name must not be an image ID for source=build' in fail_4.msg" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/docker_image.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/docker_image.yml new file mode 100644 index 000000000..a13eb691f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/docker_image.yml @@ -0,0 +1,259 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering image name + set_fact: + iname: "{{ name_prefix ~ '-options' }}" + +- name: Determining pushed image names + set_fact: + hello_world_image_base: "{{ registry_address }}/test/hello-world" + test_image_base: "{{ registry_address }}/test/{{ iname }}" + +- name: Registering image name + set_fact: + inames: "{{ inames + [iname, test_image_base ~ ':latest', test_image_base ~ ':other', hello_world_image_base ~ ':latest', hello_world_image_base ~ ':newtag', hello_world_image_base ~ ':newtag2'] }}" + +#################################################################### +## interact with test registry ##################################### +#################################################################### + +- name: Make sure image is not there + docker_image: + name: "{{ hello_world_image_base }}:latest" + state: absent + force_absent: true + +- name: Make sure we have {{ docker_test_image_hello_world }} + docker_image: + name: "{{ docker_test_image_hello_world }}" + source: pull + +- name: Push image to test registry + docker_image: + name: "{{ docker_test_image_hello_world }}" + repository: "{{ hello_world_image_base }}:latest" + push: true + source: local + register: push_1 + +- name: Push image to test registry (idempotent) + docker_image: + name: "{{ docker_test_image_hello_world }}" + repository: "{{ hello_world_image_base }}:latest" + push: true + source: local + register: push_2 + +- name: Push image to test registry (force, still idempotent) + docker_image: + name: "{{ docker_test_image_hello_world }}" + repository: "{{ hello_world_image_base }}:latest" + push: true + source: local + force_tag: true + register: push_3 + +- assert: + that: + - push_1 is changed + - push_2 is not changed + - push_3 is not changed + +- name: Get facts of local image + docker_image_info: + name: "{{ hello_world_image_base }}:latest" + register: facts_1 + +- name: Make sure image is not there + docker_image: + name: "{{ hello_world_image_base }}:latest" + state: absent + force_absent: true + +- name: Get facts of local image (absent) + docker_image_info: + name: "{{ hello_world_image_base }}:latest" + register: facts_2 + +- name: Pull image from test registry + docker_image: + name: "{{ hello_world_image_base }}:latest" + state: present + source: pull + register: pull_1 + +- name: Pull image from test registry (idempotency) + docker_image: + name: "{{ hello_world_image_base }}:latest" + state: present + source: pull + register: pull_2 + +- name: Get facts of local image (present) + docker_image_info: + name: "{{ hello_world_image_base }}:latest" + register: facts_3 + +- assert: + that: + - pull_1 is changed + - pull_2 is not changed + - facts_1.images | length == 1 + - facts_2.images | length == 0 + - facts_3.images | length == 1 + +- name: Pull image from test registry (with digest) + docker_image: + name: "{{ facts_3.images[0].RepoDigests[0] }}" + state: present + source: pull + force_source: true + register: pull_digest + +- name: Make sure that changed is still false + assert: + that: + - pull_digest is not changed + +- name: Tag different image with new tag + docker_image: + name: "{{ docker_test_image_alpine_different }}" + repository: "{{ hello_world_image_base }}:newtag" + push: false + source: pull + +- name: Push different image with new tag + docker_image: + name: "{{ hello_world_image_base }}" + repository: "{{ hello_world_image_base }}" + tag: newtag + push: true + source: local + register: push_1_different + +- name: Push different image with new tag (idempotent) + docker_image: + name: "{{ hello_world_image_base }}" + repository: "{{ hello_world_image_base }}" + tag: newtag + push: true + source: local + register: push_2_different + +- assert: + that: + - push_1_different is changed + - push_2_different is not changed + +- name: Tag same image with new tag + docker_image: + name: "{{ docker_test_image_alpine_different }}" + repository: "{{ hello_world_image_base }}:newtag2" + push: false + source: pull + +- name: Push same image with new tag + docker_image: + name: "{{ hello_world_image_base }}" + repository: "{{ hello_world_image_base }}" + tag: newtag2 + push: true + source: local + register: push_1_same + +- name: Push same image with new tag (idempotent) + docker_image: + name: "{{ hello_world_image_base }}" + repository: "{{ hello_world_image_base }}" + tag: newtag2 + push: true + source: local + register: push_2_same + +- assert: + that: + # NOTE: This should be: + # - push_1_same is changed + # Unfortunately docker does *NOT* report whether the tag already existed or not. + # Here are the logs returned by client.push() for both tasks (which are exactly the same): + # push_1_same: + # {"status": "The push refers to repository [localhost:32796/test/hello-world]"}, + # {"id": "3fc64803ca2d", "progressDetail": {}, "status": "Preparing"}, + # {"id": "3fc64803ca2d", "progressDetail": {}, "status": "Layer already exists"}, + # {"status": "newtag2: digest: sha256:92251458088c638061cda8fd8b403b76d661a4dc6b7ee71b6affcf1872557b2b size: 528"}, + # {"aux": {"Digest": "sha256:92251458088c638061cda8fd8b403b76d661a4dc6b7ee71b6affcf1872557b2b", "Size": 528, "Tag": "newtag2"}, "progressDetail": {}} + # push_2_same: + # {"status": "The push refers to repository [localhost:32796/test/hello-world]"}, + # {"id": "3fc64803ca2d", "progressDetail": {}, "status": "Preparing"}, + # {"id": "3fc64803ca2d", "progressDetail": {}, "status": "Layer already exists"}, + # {"status": "newtag2: digest: sha256:92251458088c638061cda8fd8b403b76d661a4dc6b7ee71b6affcf1872557b2b size: 528"}, + # {"aux": {"Digest": "sha256:92251458088c638061cda8fd8b403b76d661a4dc6b7ee71b6affcf1872557b2b", "Size": 528, "Tag": "newtag2"}, "progressDetail": {}} + - push_1_same is not changed + - push_2_same is not changed + +#################################################################### +## repository ###################################################### +#################################################################### + +- name: Make sure image is not there + docker_image: + name: "{{ test_image_base }}:latest" + state: absent + force_absent: true + +- name: repository + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + pull: false + repository: "{{ test_image_base }}" + source: build + register: repository_1 + +- name: repository (idempotent) + docker_image: + name: "{{ iname }}" + repository: "{{ test_image_base }}" + source: local + register: repository_2 + +- name: repository, tag with ID + docker_image: + name: "{{ repository_1.image.Id }}" + repository: "{{ test_image_base }}:other" + source: local + register: repository_3 + +- name: repository, tag with ID (idempotent) + docker_image: + name: "{{ repository_1.image.Id }}" + repository: "{{ test_image_base }}:other" + source: local + force_tag: true + register: repository_4 + +- assert: + that: + - repository_1 is changed + - repository_2 is not changed + - repository_3 is changed + - repository_4 is not changed + +- name: Get facts of image + docker_image_info: + name: "{{ test_image_base }}:latest" + register: facts_1 + +- name: cleanup + docker_image: + name: "{{ test_image_base }}:latest" + state: absent + force_absent: true + +- assert: + that: + - facts_1.images | length == 1 diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/options.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/options.yml new file mode 100644 index 000000000..0670f1332 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/tasks/tests/options.yml @@ -0,0 +1,446 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering image name + set_fact: + iname: "{{ name_prefix ~ '-options' }}" + iname_1: "{{ name_prefix ~ '-options-1' }}" + hello_world_alt: "{{ name_prefix }}-hello-world-alt:v1.2.3-foo" + +- name: Registering image name + set_fact: + inames: "{{ inames + [iname, iname_1, hello_world_alt] }}" + +#################################################################### +## build.args ###################################################### +#################################################################### + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- name: buildargs + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + dockerfile: "ArgsDockerfile" + args: + IMAGE: "{{ docker_test_image_busybox }}" + TEST1: val1 + TEST2: val2 + TEST3: "True" + pull: false + source: build + register: buildargs_1 + ignore_errors: true + +- name: buildargs (idempotency) + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + dockerfile: "ArgsDockerfile" + args: + IMAGE: "{{ docker_test_image_busybox }}" + TEST1: val1 + TEST2: val2 + TEST3: "True" + pull: false + source: build + register: buildargs_2 + ignore_errors: true + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- assert: + that: + - buildargs_1 is changed + - buildargs_2 is not failed and buildargs_2 is not changed + +#################################################################### +## build.container_limits ########################################## +#################################################################### + +- name: container_limits (Failed due to min memory limit) + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + container_limits: + memory: 4000 + pull: false + source: build + ignore_errors: true + register: container_limits_1 + +- name: container_limits + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + container_limits: + memory: 7000000 + memswap: 8000000 + pull: false + source: build + register: container_limits_2 + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- assert: + that: + # It *sometimes* happens that the first task does not fail. + # For now, we work around this by + # a) requiring that if it fails, the message must + # contain 'Minimum memory limit allowed is (4|6)MB', and + # b) requiring that either the first task, or the second + # task is changed, but not both. + - "not container_limits_1 is failed or ('Minimum memory limit allowed is ') in container_limits_1.msg" + - "container_limits_1 is changed or container_limits_2 is changed and not (container_limits_1 is changed and container_limits_2 is changed)" + +#################################################################### +## build.dockerfile ################################################ +#################################################################### + +- name: dockerfile + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + dockerfile: "MyDockerfile" + pull: false + source: build + register: dockerfile_1 + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- assert: + that: + - dockerfile_1 is changed + - "('FROM ' ~ docker_test_image_alpine) in dockerfile_1.stdout" + - dockerfile_1['image']['Config']['WorkingDir'] == '/newdata' + +#################################################################### +## build.platform ################################################## +#################################################################### + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- name: build.platform + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + platform: linux + pull: false + source: build + register: platform_1 + ignore_errors: true + +- name: build.platform (idempotency) + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + platform: linux + pull: false + source: build + register: platform_2 + ignore_errors: true + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- assert: + that: + - platform_1 is changed + - platform_2 is not failed and platform_2 is not changed + +#################################################################### +## force ########################################################### +#################################################################### + +- name: Build an image + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + pull: false + source: build + +- name: force (changed) + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + dockerfile: "MyDockerfile" + pull: false + source: build + force_source: true + register: force_1 + +- name: force (unchanged) + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + dockerfile: "MyDockerfile" + pull: false + source: build + force_source: true + register: force_2 + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- assert: + that: + - force_1 is changed + - force_2 is not changed + +#################################################################### +## load path ####################################################### +#################################################################### + +- name: Archive image + docker_image: + name: "{{ docker_test_image_hello_world }}" + archive_path: "{{ remote_tmp_dir }}/image.tar" + source: pull + register: archive_image + +- assert: + that: + - archive_image is changed + +- name: Copy archive because we will mutate it but other tests need the original + copy: + remote_src: true + src: "{{ remote_tmp_dir }}/image.tar" + dest: "{{ remote_tmp_dir }}/image_mutated.tar" + +- name: Archive image again (idempotent) + docker_image: + name: "{{ docker_test_image_hello_world }}" + archive_path: "{{ remote_tmp_dir }}/image_mutated.tar" + source: local + register: archive_image_2 + +- assert: + that: + - archive_image_2 is not changed + +- name: Archive image 3rd time, should overwrite due to different id + docker_image: + name: "{{ docker_test_image_alpine_different }}" + archive_path: "{{ remote_tmp_dir }}/image_mutated.tar" + source: pull + register: archive_image_3 + +- assert: + that: + - archive_image_3 is changed + +- name: Reset archive + copy: + remote_src: true + src: "{{ remote_tmp_dir }}/image.tar" + dest: "{{ remote_tmp_dir }}/image_mutated.tar" + +- name: Tag image with different name + docker_image: + name: "{{ docker_test_image_hello_world }}" + repository: "{{ hello_world_alt }}" + source: local + +- name: Archive image 4th time, should overwrite due to different name even when ID is same + docker_image: + name: "{{ hello_world_alt }}" + # Tagged as docker_test_image_hello_world but has same hash/id (before this task overwrites it) + archive_path: "{{ remote_tmp_dir }}/image_mutated.tar" + source: local + register: archive_image_4 + +- assert: + that: + - archive_image_4 is changed + +# This is the test that needs the original, non-mutated archive +- name: Archive image by ID + docker_image: + name: "{{ archive_image.image.Id }}" + archive_path: "{{ remote_tmp_dir }}/image_id.tar" + source: local + register: archive_image_id + +- name: Create invalid archive + copy: + dest: "{{ remote_tmp_dir }}/image-invalid.tar" + content: "this is not a valid image" + +- name: remove image + docker_image: + name: "{{ docker_test_image_hello_world }}" + state: absent + force_absent: true + +- name: load image (changed) + docker_image: + name: "{{ docker_test_image_hello_world }}" + load_path: "{{ remote_tmp_dir }}/image.tar" + source: load + register: load_image + +- name: load image (idempotency) + docker_image: + name: "{{ docker_test_image_hello_world }}" + load_path: "{{ remote_tmp_dir }}/image.tar" + source: load + register: load_image_1 + +- name: load image (wrong name) + docker_image: + name: foo:bar + load_path: "{{ remote_tmp_dir }}/image.tar" + source: load + register: load_image_2 + ignore_errors: true + +- name: load image (invalid image) + docker_image: + name: foo:bar + load_path: "{{ remote_tmp_dir }}/image-invalid.tar" + source: load + register: load_image_3 + ignore_errors: true + +- name: load image (ID, idempotency) + docker_image: + name: "{{ archive_image.image.Id }}" + load_path: "{{ remote_tmp_dir }}/image_id.tar" + source: load + register: load_image_4 + +- assert: + that: + - load_image is changed + - archive_image['image']['Id'] == load_image['image']['Id'] + - load_image_1 is not changed + - load_image_2 is failed + - >- + "The archive did not contain image 'foo:bar'. Instead, found '" ~ docker_test_image_hello_world ~ "'." == load_image_2.msg + - load_image_3 is failed + - '"Detected no loaded images. Archive potentially corrupt?" == load_image_3.msg' + - load_image_4 is not changed + +#################################################################### +## build.path ###################################################### +#################################################################### + +- name: Build image + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + pull: false + source: build + register: path_1 + +- name: Build image (idempotency) + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + pull: false + source: build + register: path_2 + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- assert: + that: + - path_1 is changed + - path_2 is not changed + +#################################################################### +## build.target #################################################### +#################################################################### + +- name: Build multi-stage image + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + dockerfile: "StagedDockerfile" + target: first + pull: false + source: build + register: dockerfile_2 + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- assert: + that: + - dockerfile_2 is changed + - dockerfile_2.image.Config.WorkingDir == '/first' + +#################################################################### +## build.etc_hosts ################################################# +#################################################################### + +- name: Build image with custom etc_hosts + docker_image: + name: "{{ iname }}" + build: + path: "{{ remote_tmp_dir }}/files" + dockerfile: "EtcHostsDockerfile" + pull: false + etc_hosts: + some-custom-host: "127.0.0.1" + source: build + register: path_1 + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: true + +- assert: + that: + - path_1 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/ArgsDockerfile b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/ArgsDockerfile new file mode 100644 index 000000000..dedd88a8f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/ArgsDockerfile @@ -0,0 +1,13 @@ +# Copyright (c) 2022, Felix Fontein +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +ARG IMAGE +ARG TEST1 +ARG TEST2 +ARG TEST3 + +FROM ${IMAGE} +ENV foo /bar +WORKDIR ${foo} +RUN echo "${TEST1} - ${TEST2} - ${TEST3}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/Dockerfile b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/Dockerfile new file mode 100644 index 000000000..286094b9e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/Dockerfile @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +FROM {{ docker_test_image_busybox }} +ENV foo /bar +WORKDIR ${foo} diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/EtcHostsDockerfile b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/EtcHostsDockerfile new file mode 100644 index 000000000..bc21b966b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/EtcHostsDockerfile @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +FROM {{ docker_test_image_busybox }} +# This should fail building if docker cannot resolve some-custom-host +RUN ping -c1 some-custom-host diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/MyDockerfile b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/MyDockerfile new file mode 100644 index 000000000..24b1c926f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/MyDockerfile @@ -0,0 +1,9 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +FROM {{ docker_test_image_alpine }} +ENV INSTALL_PATH /newdata +RUN mkdir -p $INSTALL_PATH + +WORKDIR $INSTALL_PATH diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/StagedDockerfile b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/StagedDockerfile new file mode 100644 index 000000000..da2253425 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image/templates/StagedDockerfile @@ -0,0 +1,11 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +FROM {{ docker_test_image_busybox }} AS first +ENV dir /first +WORKDIR ${dir} + +FROM {{ docker_test_image_busybox }} AS second +ENV dir /second +WORKDIR ${dir} diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_image_info/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image_info/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_info/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image_info/tasks/main.yml new file mode 100644 index 000000000..5bd053ac4 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_info/tasks/main.yml @@ -0,0 +1,63 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Make sure image is not there + docker_image: + name: "{{ docker_test_image_alpine_different }}" + state: absent + + - name: Inspect a non-available image + docker_image_info: + name: "{{ docker_test_image_alpine_different }}" + register: result + + - assert: + that: + - "result.images|length == 0" + + - name: Make sure images are there + docker_image: + name: "{{ item }}" + source: pull + state: present + loop: + - "{{ docker_test_image_hello_world }}" + - "{{ docker_test_image_alpine }}" + + - name: Inspect an available image + docker_image_info: + name: "{{ docker_test_image_hello_world }}" + register: result + + - assert: + that: + - "result.images|length == 1" + - "docker_test_image_hello_world in result.images[0].RepoTags" + + - name: Inspect multiple images + docker_image_info: + name: + - "{{ docker_test_image_hello_world }}" + - "{{ docker_test_image_alpine }}" + register: result + + - debug: var=result + + - assert: + that: + - "result.images|length == 2" + - "docker_test_image_hello_world in result.images[0].RepoTags" + - "docker_test_image_alpine in result.images[1].RepoTags" + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_image_info tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_load/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_load/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/meta/main.yml new file mode 100644 index 000000000..2650229d8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/main.yml new file mode 100644 index 000000000..88b23cfe7 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/main.yml @@ -0,0 +1,13 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6'] + include_tasks: + file: test.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/test.yml new file mode 100644 index 000000000..a56c95301 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/test.yml @@ -0,0 +1,38 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create random name prefix + set_fact: + name_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" +- name: Create image and container list + set_fact: + inames: [] + cnames: [] + +- debug: + msg: "Using name prefix {{ name_prefix }}" + +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: "Make sure all images are removed" + docker_image: + name: "{{ item }}" + state: absent + with_items: "{{ inames }}" + - name: "Make sure all containers are removed" + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + with_items: "{{ cnames }}" + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_image tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/tests/basic.yml b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/tests/basic.yml new file mode 100644 index 000000000..8d9de9948 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_image_load/tasks/tests/basic.yml @@ -0,0 +1,217 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- set_fact: + image_names: + - "{{ docker_test_image_hello_world }}" + - "{{ docker_test_image_alpine_different }}" + - "{{ docker_test_image_alpine }}" + +- name: Make sure images are there + docker_image: + name: "{{ item }}" + source: pull + register: images + loop: "{{ image_names }}" + +- name: Compile list of all image names and IDs + set_fact: + image_ids: "{{ images.results | map(attribute='image') | map(attribute='Id') | list }}" + all_images: "{{ image_names + (images.results | map(attribute='image') | map(attribute='Id') | list) }}" + +- name: Create archives + command: docker save {{ item.images | join(' ') }} -o {{ remote_tmp_dir }}/{{ item.file }} + loop: + - file: archive-1.tar + images: "{{ image_names }}" + - file: archive-2.tar + images: "{{ image_ids }}" + - file: archive-3.tar + images: + - "{{ image_names[0] }}" + - "{{ image_ids[1] }}" + - file: archive-4.tar + images: + - "{{ image_ids[0] }}" + - "{{ image_names[0] }}" + - file: archive-5.tar + images: + - "{{ image_ids[0] }}" + +# All images by IDs + +- name: Remove all images + docker_image: + name: "{{ item }}" + state: absent + force_absent: true + loop: "{{ all_images }}" + ignore_errors: true + register: remove_all_images + +- name: Prune all containers (if removing failed) + docker_prune: + containers: true + when: remove_all_images is failed + +- name: Obtain all docker containers and images (if removing failed) + shell: docker ps -a ; docker images -a + when: remove_all_images is failed + register: docker_container_image_list + +- name: Show all docker containers and images (if removing failed) + debug: + var: docker_container_image_list.stdout_lines + when: remove_all_images is failed + +- name: Remove all images (after pruning) + docker_image: + name: "{{ item }}" + state: absent + force_absent: true + loop: "{{ all_images }}" + when: remove_all_images is failed + +- name: Load all images (IDs) + docker_image_load: + path: "{{ remote_tmp_dir }}/archive-2.tar" + register: result + +- name: Print loaded image names + debug: + var: result.image_names + +- assert: + that: + - result is changed + - result.image_names | sort == image_ids | sort + - result.image_names | length == result.images | length + +- name: Load all images (IDs, should be same result) + docker_image_load: + path: "{{ remote_tmp_dir }}/archive-2.tar" + register: result_2 + +- name: Print loaded image names + debug: + var: result_2.image_names + +- assert: + that: + - result_2 is changed + - result_2.image_names | sort == image_ids | sort + - result_2.image_names | length == result_2.images | length + +# Mixed images and IDs + +- name: Remove all images + docker_image: + name: "{{ item }}" + state: absent + loop: "{{ all_images }}" + +- name: Load all images (mixed images and IDs) + docker_image_load: + path: "{{ remote_tmp_dir }}/archive-3.tar" + register: result + +- name: Print loading log + debug: + var: result.stdout_lines + +- name: Print loaded image names + debug: + var: result.image_names + +- assert: + that: + - result is changed + # For some reason, *sometimes* only the named image is found; in fact, in that case, the log only mentions that image and nothing else + - "result.images | length == 3 or ('Loaded image: ' ~ docker_test_image_hello_world) == result.stdout" + - (result.image_names | sort) in [[image_names[0], image_ids[0], image_ids[1]] | sort, [image_names[0]]] + - result.images | length in [1, 3] + - (result.images | map(attribute='Id') | sort) in [[image_ids[0], image_ids[0], image_ids[1]] | sort, [image_ids[0]]] + +# Same image twice + +- name: Remove all images + docker_image: + name: "{{ item }}" + state: absent + loop: "{{ all_images }}" + +- name: Load all images (same image twice) + docker_image_load: + path: "{{ remote_tmp_dir }}/archive-4.tar" + register: result + +- name: Print loaded image names + debug: + var: result.image_names + +- assert: + that: + - result is changed + - result.image_names | length == 1 + - result.image_names[0] == image_names[0] + - result.images | length == 1 + - result.images[0].Id == image_ids[0] + +# Single image by ID + +- name: Remove all images + docker_image: + name: "{{ item }}" + state: absent + loop: "{{ all_images }}" + +- name: Load all images (single image by ID) + docker_image_load: + path: "{{ remote_tmp_dir }}/archive-5.tar" + register: result + +- name: Print loaded image names + debug: + var: result.image_names + +- assert: + that: + - result is changed + - result.image_names | length == 1 + - result.image_names[0] == image_ids[0] + - result.images | length == 1 + - result.images[0].Id == image_ids[0] + +- name: Try to get image info by name + docker_image_info: + name: "{{ image_names[0] }}" + register: result + +- name: Make sure that image does not exist by name + assert: + that: + - result.images | length == 0 + +# All images by names + +- name: Remove all images + docker_image: + name: "{{ item }}" + state: absent + loop: "{{ all_images }}" + +- name: Load all images (names) + docker_image_load: + path: "{{ remote_tmp_dir }}/archive-1.tar" + register: result + +- name: Print loaded image names + debug: + var: result.image_names + +- assert: + that: + - result.image_names | sort == image_names | sort + - result.image_names | length == result.images | length diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_login/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_login/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_login/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_login/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_login/meta/main.yml new file mode 100644 index 000000000..3133a0360 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_login/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker_registry diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/main.yml new file mode 100644 index 000000000..88b23cfe7 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/main.yml @@ -0,0 +1,13 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6'] + include_tasks: + file: test.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/test.yml new file mode 100644 index 000000000..bd99acc0a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/test.yml @@ -0,0 +1,13 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_image tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/tests/docker_login.yml b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/tests/docker_login.yml new file mode 100644 index 000000000..efb3efc15 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/tests/docker_login.yml @@ -0,0 +1,150 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Log out server + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: hunter2 + state: absent + + - name: Log in with wrong password (check mode) + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: "1234" + state: present + register: login_failed_check + ignore_errors: true + check_mode: true + + - name: Log in with wrong password + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: "1234" + state: present + register: login_failed + ignore_errors: true + + - name: Make sure that login failed + assert: + that: + - login_failed_check is failed + - "('login attempt to http://' ~ registry_frontend_address ~ '/v2/ failed') in login_failed_check.msg" + - login_failed is failed + - "('login attempt to http://' ~ registry_frontend_address ~ '/v2/ failed') in login_failed.msg" + + - name: Log in (check mode) + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: hunter2 + state: present + register: login_1 + check_mode: true + + - name: Log in + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: hunter2 + state: present + register: login_2 + + - name: Get permissions of ~/.docker/config.json + stat: + path: ~/.docker/config.json + register: login_2_stat + + - name: Log in (idempotent) + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: hunter2 + state: present + register: login_3 + + - name: Log in (idempotent, check mode) + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: hunter2 + state: present + register: login_4 + check_mode: true + + - name: Make sure that login worked + assert: + that: + - login_1 is changed + - login_2 is changed + - login_3 is not changed + - login_4 is not changed + - login_2_stat.stat.mode == '0600' + + - name: Log in again with wrong password (check mode) + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: "1234" + state: present + register: login_failed_check + ignore_errors: true + check_mode: true + + - name: Log in again with wrong password + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: "1234" + state: present + register: login_failed + ignore_errors: true + + - name: Make sure that login failed again + assert: + that: + - login_failed_check is failed + - "('login attempt to http://' ~ registry_frontend_address ~ '/v2/ failed') in login_failed_check.msg" + - login_failed is failed + - "('login attempt to http://' ~ registry_frontend_address ~ '/v2/ failed') in login_failed.msg" + + - name: Log out (check mode) + docker_login: + registry_url: "{{ registry_frontend_address }}" + state: absent + register: logout_1 + check_mode: true + + - name: Log out + docker_login: + registry_url: "{{ registry_frontend_address }}" + state: absent + register: logout_2 + + - name: Log out (idempotent) + docker_login: + registry_url: "{{ registry_frontend_address }}" + state: absent + register: logout_3 + + - name: Log out (idempotent, check mode) + docker_login: + registry_url: "{{ registry_frontend_address }}" + state: absent + register: logout_4 + check_mode: true + + - name: Make sure that login worked + assert: + that: + - logout_1 is changed + - logout_2 is changed + - logout_3 is not changed + - logout_4 is not changed + + when: registry_frontend_address != 'n/a' diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/tests/multiple-servers.yml b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/tests/multiple-servers.yml new file mode 100644 index 000000000..7ffd0978e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_login/tasks/tests/multiple-servers.yml @@ -0,0 +1,61 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Log out server 1 + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: hunter2 + state: absent + + - name: Log out server 2 + docker_login: + registry_url: "{{ registry_frontend2_address }}" + username: testuser + password: hunter2 + state: absent + + - name: Log in server 1 + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: hunter2 + state: present + register: login_1 + + - name: Log in server 2 + docker_login: + registry_url: "{{ registry_frontend2_address }}" + username: testuser + password: hunter2 + state: present + register: login_2 + + - name: Log in server 1 (idempotent) + docker_login: + registry_url: "{{ registry_frontend_address }}" + username: testuser + password: hunter2 + state: present + register: login_1_2 + + - name: Log in server 2 (idempotent) + docker_login: + registry_url: "{{ registry_frontend2_address }}" + username: testuser + password: hunter2 + state: present + register: login_2_2 + + - name: Make sure that login worked + assert: + that: + - login_1 is changed + - login_2 is changed + - login_1_2 is not changed + - login_2_2 is not changed + + when: registry_frontend_address != 'n/a' and registry_frontend2_address != 'n/a' diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_network/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/main.yml new file mode 100644 index 000000000..4a056151b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/main.yml @@ -0,0 +1,52 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: List inspection results for all docker networks + docker_host_info: + networks: true + verbose_output: true + register: all_networks + +- name: Show inspection results for all docker networks + debug: + var: all_networks.networks + +- name: Create random name prefix + set_fact: + name_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + cnames: [] + dnetworks: [] + +- debug: + msg: "Using name prefix {{ name_prefix }}" + +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: "Make sure all containers are removed" + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + loop: "{{ cnames }}" + - name: "Make sure all networks are removed" + docker_network: + name: "{{ item }}" + state: absent + force: true + loop: "{{ dnetworks }}" + + when: docker_api_version is version('1.25', '>=') # FIXME: find out API version! + +- fail: msg="Too old docker / docker-py version to run docker_network tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/basic.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/basic.yml new file mode 100644 index 000000000..1a419c733 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/basic.yml @@ -0,0 +1,138 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container and network names + set_fact: + cname_1: "{{ name_prefix ~ '-container-1' }}" + cname_2: "{{ name_prefix ~ '-container-2' }}" + cname_3: "{{ name_prefix ~ '-container-3' }}" + nname_1: "{{ name_prefix ~ '-network-1' }}" + nname_2: "{{ name_prefix ~ '-network-2' }}" +- name: Registering container and network names + set_fact: + cnames: "{{ cnames + [cname_1, cname_2, cname_3] }}" + dnetworks: "{{ dnetworks + [nname_1, nname_2] }}" + +- name: Create containers + docker_container: + name: "{{ container_name }}" + image: "{{ docker_test_image_alpine }}" + command: /bin/sleep 10m + state: started + loop: + - "{{ cname_1 }}" + - "{{ cname_2 }}" + - "{{ cname_3 }}" + loop_control: + loop_var: container_name + +#################################################################### + +- name: Create network + docker_network: + name: "{{ nname_1 }}" + state: present + register: networks_1 + +- name: Connect network to containers 1 + docker_network: + name: "{{ nname_1 }}" + state: present + connected: + - "{{ cname_1 }}" + register: networks_2 + +- name: Connect network to containers 1 (idempotency) + docker_network: + name: "{{ nname_1 }}" + state: present + connected: + - "{{ cname_1 }}" + register: networks_2_idem + +- name: Connect network to containers 1 and 2 + docker_network: + name: "{{ nname_1 }}" + state: present + connected: + - "{{ cname_1 }}" + - "{{ cname_2 }}" + register: networks_3 + +- name: Connect network to containers 1 and 2 (idempotency) + docker_network: + name: "{{ nname_1 }}" + state: present + connected: + - "{{ cname_1 }}" + - "{{ cname_2 }}" + register: networks_3_idem + +- name: Connect network to container 3 + docker_network: + name: "{{ nname_1 }}" + state: present + connected: + - "{{ cname_3 }}" + appends: true + register: networks_4 + +- name: Connect network to container 3 (idempotency) + docker_network: + name: "{{ nname_1 }}" + state: present + connected: + - "{{ cname_3 }}" + appends: true + register: networks_4_idem + +- name: Disconnect network from container 1 + docker_network: + name: "{{ nname_1 }}" + state: present + connected: + - "{{ cname_2 }}" + - "{{ cname_3 }}" + register: networks_5 + +- name: Disconnect network from container 1 (idempotency) + docker_network: + name: "{{ nname_1 }}" + state: present + connected: + - "{{ cname_2 }}" + - "{{ cname_3 }}" + register: networks_5_idem + +- name: Cleanup + docker_network: + name: "{{ nname_1 }}" + state: absent + +- assert: + that: + - networks_1 is changed + - networks_2 is changed + - networks_2_idem is not changed + - networks_3 is changed + - networks_3_idem is not changed + - networks_4 is changed + - networks_4_idem is not changed + - networks_5 is changed + - networks_5_idem is not changed + +#################################################################### + +- name: Delete containers + docker_container: + name: "{{ container_name }}" + state: absent + force_kill: true + loop: + - "{{ cname_1 }}" + - "{{ cname_2 }}" + - "{{ cname_3 }}" + loop_control: + loop_var: container_name diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/ipam.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/ipam.yml new file mode 100644 index 000000000..7091e95fb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/ipam.yml @@ -0,0 +1,309 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering network names + set_fact: + nname_ipam_0: "{{ name_prefix ~ '-network-ipam-0' }}" + nname_ipam_1: "{{ name_prefix ~ '-network-ipam-1' }}" + nname_ipam_2: "{{ name_prefix ~ '-network-ipam-2' }}" + nname_ipam_3: "{{ name_prefix ~ '-network-ipam-3' }}" + +- name: Registering network names + set_fact: + dnetworks: "{{ dnetworks + [nname_ipam_0, nname_ipam_1, nname_ipam_2, nname_ipam_3] }}" + + +#################### IPv4 IPAM config #################### + +- name: Create network with custom IPAM config + docker_network: + name: "{{ nname_ipam_1 }}" + ipam_config: + - subnet: 10.25.120.0/24 + gateway: 10.25.120.2 + iprange: 10.25.120.0/26 + aux_addresses: + host1: 10.25.120.3 + host2: 10.25.120.4 + register: network + +- assert: + that: + - network is changed + +- name: Create network with custom IPAM config (idempotence) + docker_network: + name: "{{ nname_ipam_1 }}" + ipam_config: + - subnet: 10.25.120.0/24 + gateway: 10.25.120.2 + iprange: 10.25.120.0/26 + aux_addresses: + host1: 10.25.120.3 + host2: 10.25.120.4 + register: network + +- assert: + that: + - network is not changed + +- name: Change of network created with custom IPAM config + docker_network: + name: "{{ nname_ipam_1 }}" + ipam_config: + - subnet: 10.25.121.0/24 + gateway: 10.25.121.2 + iprange: 10.25.121.0/26 + aux_addresses: + host1: 10.25.121.3 + register: network + diff: true + +- assert: + that: + - network is changed + - network.diff.differences | length == 4 + - '"ipam_config[0].subnet" in network.diff.differences' + - '"ipam_config[0].gateway" in network.diff.differences' + - '"ipam_config[0].iprange" in network.diff.differences' + - '"ipam_config[0].aux_addresses" in network.diff.differences' + +- name: Remove gateway and iprange of network with custom IPAM config + docker_network: + name: "{{ nname_ipam_1 }}" + ipam_config: + - subnet: 10.25.121.0/24 + register: network + +- assert: + that: + - network is not changed + +- name: Cleanup network with custom IPAM config + docker_network: + name: "{{ nname_ipam_1 }}" + state: absent + + +#################### IPv6 IPAM config #################### + +- name: Create network with IPv6 IPAM config + docker_network: + name: "{{ nname_ipam_2 }}" + enable_ipv6: true + ipam_config: + - subnet: fdd1:ac8c:0557:7ce0::/64 + register: network + +- assert: + that: + - network is changed + +- name: Create network with IPv6 IPAM config (idempotence) + docker_network: + name: "{{ nname_ipam_2 }}" + enable_ipv6: true + ipam_config: + - subnet: fdd1:ac8c:0557:7ce0::/64 + register: network + +- assert: + that: + - network is not changed + +- name: Change subnet of network with IPv6 IPAM config + docker_network: + name: "{{ nname_ipam_2 }}" + enable_ipv6: true + ipam_config: + - subnet: fdd1:ac8c:0557:7ce1::/64 + register: network + diff: true + +- assert: + that: + - network is changed + - network.diff.differences | length == 1 + - network.diff.differences[0] == "ipam_config[0].subnet" + +- name: Change subnet of network with IPv6 IPAM config + docker_network: + name: "{{ nname_ipam_2 }}" + enable_ipv6: true + ipam_config: + - subnet: "fdd1:ac8c:0557:7ce1::" + register: network + ignore_errors: true + +- assert: + that: + - network is failed + - "network.msg == '\"fdd1:ac8c:0557:7ce1::\" is not a valid CIDR'" + +- name: Cleanup network with IPv6 IPAM config + docker_network: + name: "{{ nname_ipam_2 }}" + state: absent + + +#################### IPv4 and IPv6 network #################### + +- name: Create network with IPv6 and custom IPv4 IPAM config + docker_network: + name: "{{ nname_ipam_3 }}" + enable_ipv6: true + ipam_config: + - subnet: 10.26.120.0/24 + - subnet: fdd1:ac8c:0557:7ce2::/64 + register: network + +- assert: + that: + - network is changed + +- name: Change subnet order of network with IPv6 and custom IPv4 IPAM config (idempotence) + docker_network: + name: "{{ nname_ipam_3 }}" + enable_ipv6: true + ipam_config: + - subnet: fdd1:ac8c:0557:7ce2::/64 + - subnet: 10.26.120.0/24 + register: network + +- assert: + that: + - network is not changed + +- name: Remove IPv6 from network with custom IPv4 and IPv6 IPAM config (change) + docker_network: + name: "{{ nname_ipam_3 }}" + enable_ipv6: false + ipam_config: + - subnet: 10.26.120.0/24 + register: network + diff: true + +- assert: + that: + - network is changed + - network.diff.differences | length == 1 + - network.diff.differences[0] == "enable_ipv6" + +- name: Cleanup network with IPv6 and custom IPv4 IPAM config + docker_network: + name: "{{ nname_ipam_3 }}" + state: absent + + +#################### multiple IPv4 networks #################### + +- block: + - name: Create network with two IPv4 IPAM configs + docker_network: + name: "{{ nname_ipam_3 }}" + driver: "macvlan" + driver_options: + parent: "{{ ansible_default_ipv4.alias }}" + ipam_config: + - subnet: 10.26.120.0/24 + - subnet: 10.26.121.0/24 + register: network + + - assert: + that: + - network is changed + + - name: Create network with two IPv4 IPAM configs (idempotence) + docker_network: + name: "{{ nname_ipam_3 }}" + driver: "macvlan" + driver_options: + parent: "{{ ansible_default_ipv4.alias }}" + ipam_config: + - subnet: 10.26.121.0/24 + - subnet: 10.26.120.0/24 + register: network + + - assert: + that: + - network is not changed + + - name: Create network with two IPv4 IPAM configs (change) + docker_network: + name: "{{ nname_ipam_3 }}" + driver: "macvlan" + driver_options: + parent: "{{ ansible_default_ipv4.alias }}" + ipam_config: + - subnet: 10.26.120.0/24 + - subnet: 10.26.122.0/24 + register: network + diff: true + + - assert: + that: + - network is changed + - network.diff.differences | length == 1 + + - name: Create network with one IPv4 IPAM config (no change) + docker_network: + name: "{{ nname_ipam_3 }}" + driver: "macvlan" + driver_options: + parent: "{{ ansible_default_ipv4.alias }}" + ipam_config: + - subnet: 10.26.122.0/24 + register: network + + - assert: + that: + - network is not changed + + - name: Cleanup network + docker_network: + name: "{{ nname_ipam_3 }}" + state: absent + + when: ansible_facts.virtualization_type != 'docker' and ansible_default_ipv4.alias is defined + + +#################### IPAM driver options #################### + +- name: Create network with IPAM driver options + docker_network: + name: "{{ nname_ipam_3 }}" + ipam_driver: default + ipam_driver_options: + a: b + register: network_1 + ignore_errors: true +- name: Create network with IPAM driver options (idempotence) + docker_network: + name: "{{ nname_ipam_3 }}" + ipam_driver: default + ipam_driver_options: + a: b + diff: true + register: network_2 + ignore_errors: true +- name: Create network with IPAM driver options (change) + docker_network: + name: "{{ nname_ipam_3 }}" + ipam_driver: default + ipam_driver_options: + a: c + diff: true + register: network_3 + ignore_errors: true +- name: Cleanup network + docker_network: + name: "{{ nname_ipam_3 }}" + state: absent + +- assert: + that: + - network_1 is changed + - network_2 is not changed + - network_3 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/options.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/options.yml new file mode 100644 index 000000000..d2e397399 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/options.yml @@ -0,0 +1,234 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering network name + set_fact: + nname_1: "{{ name_prefix ~ '-network-1' }}" +- name: Registering network name + set_fact: + dnetworks: "{{ dnetworks + [nname_1] }}" + +#################################################################### +## internal ######################################################## +#################################################################### + +- name: internal + docker_network: + name: "{{ nname_1 }}" + internal: true + register: internal_1 + +- name: internal (idempotency) + docker_network: + name: "{{ nname_1 }}" + internal: true + register: internal_2 + +- name: internal (change) + docker_network: + name: "{{ nname_1 }}" + internal: false + register: internal_3 + +- name: cleanup + docker_network: + name: "{{ nname_1 }}" + state: absent + force: true + +- assert: + that: + - internal_1 is changed + - internal_2 is not changed + - internal_3 is changed + +#################################################################### +## driver_options ################################################## +#################################################################### + +- name: driver_options + docker_network: + name: "{{ nname_1 }}" + driver_options: + com.docker.network.bridge.enable_icc: 'false' + register: driver_options_1 + +- name: driver_options (idempotency) + docker_network: + name: "{{ nname_1 }}" + driver_options: + com.docker.network.bridge.enable_icc: 'false' + register: driver_options_2 + +- name: driver_options (idempotency with string translation) + docker_network: + name: "{{ nname_1 }}" + driver_options: + com.docker.network.bridge.enable_icc: false + register: driver_options_3 + +- name: driver_options (change) + docker_network: + name: "{{ nname_1 }}" + driver_options: + com.docker.network.bridge.enable_icc: 'true' + register: driver_options_4 + +- name: driver_options (idempotency with string translation) + docker_network: + name: "{{ nname_1 }}" + driver_options: + com.docker.network.bridge.enable_icc: true + register: driver_options_5 + +- name: cleanup + docker_network: + name: "{{ nname_1 }}" + state: absent + force: true + +- assert: + that: + - driver_options_1 is changed + - driver_options_2 is not changed + - driver_options_3 is not changed + - driver_options_4 is changed + - driver_options_5 is not changed + +#################################################################### +## scope ########################################################### +#################################################################### + +- block: + - name: scope + docker_network: + name: "{{ nname_1 }}" + driver: bridge + scope: local + register: scope_1 + + - name: scope (idempotency) + docker_network: + name: "{{ nname_1 }}" + driver: bridge + scope: local + register: scope_2 + + - name: swarm + docker_swarm: + state: present + advertise_addr: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}" + + # Driver change alongside scope is intentional - bridge doesn't appear to support anything but local, and overlay can't downgrade to local. Additionally, overlay reports as swarm for swarm OR global, so no change is reported in that case. + # Test output indicates that the scope is altered, at least, so manual inspection will be required to verify this going forward, unless we come up with a test driver that supports multiple scopes. + - name: scope (change) + docker_network: + name: "{{ nname_1 }}" + driver: overlay + scope: swarm + register: scope_3 + + - name: cleanup network + docker_network: + name: "{{ nname_1 }}" + state: absent + force: true + + - assert: + that: + - scope_1 is changed + - scope_2 is not changed + - scope_3 is changed + + always: + - name: cleanup swarm + docker_swarm: + state: absent + force: true + +#################################################################### +## attachable ###################################################### +#################################################################### + +- name: attachable + docker_network: + name: "{{ nname_1 }}" + attachable: true + register: attachable_1 + ignore_errors: true + +- name: attachable (idempotency) + docker_network: + name: "{{ nname_1 }}" + attachable: true + register: attachable_2 + ignore_errors: true + +- name: attachable (change) + docker_network: + name: "{{ nname_1 }}" + attachable: false + register: attachable_3 + ignore_errors: true + +- name: cleanup + docker_network: + name: "{{ nname_1 }}" + state: absent + force: true + +- assert: + that: + - attachable_1 is changed + - attachable_2 is not changed + - attachable_3 is changed + +#################################################################### +## labels ########################################################## +#################################################################### + +- name: labels + docker_network: + name: "{{ nname_1 }}" + labels: + ansible.test.1: hello + ansible.test.2: world + register: labels_1 + +- name: labels (idempotency) + docker_network: + name: "{{ nname_1 }}" + labels: + ansible.test.2: world + ansible.test.1: hello + register: labels_2 + +- name: labels (less labels) + docker_network: + name: "{{ nname_1 }}" + labels: + ansible.test.1: hello + register: labels_3 + +- name: labels (more labels) + docker_network: + name: "{{ nname_1 }}" + labels: + ansible.test.1: hello + ansible.test.3: ansible + register: labels_4 + +- name: cleanup + docker_network: + name: "{{ nname_1 }}" + state: absent + force: true + +- assert: + that: + - labels_1 is changed + - labels_2 is not changed + - labels_3 is not changed + - labels_4 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/overlay.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/overlay.yml new file mode 100644 index 000000000..59d79cc08 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/overlay.yml @@ -0,0 +1,62 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering network name + set_fact: + nname_1: "{{ name_prefix ~ '-network-1' }}" +- name: Registering network name + set_fact: + dnetworks: "{{ dnetworks + [nname_1] }}" + +#################################################################### +## overlay ######################################################### +#################################################################### + +- block: + # Overlay networks require swarm initialization before they'll work + - name: swarm + docker_swarm: + state: present + advertise_addr: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}" + + - name: overlay + docker_network: + name: "{{ nname_1 }}" + driver: overlay + driver_options: + com.docker.network.driver.overlay.vxlanid_list: "257" + register: overlay_1 + + - name: overlay (idempotency) + docker_network: + name: "{{ nname_1 }}" + driver: overlay + driver_options: + com.docker.network.driver.overlay.vxlanid_list: "257" + register: overlay_2 + + - name: overlay (change) + docker_network: + name: "{{ nname_1 }}" + driver: bridge + register: overlay_3 + + - name: cleanup network + docker_network: + name: "{{ nname_1 }}" + state: absent + force: true + + - assert: + that: + - overlay_1 is changed + - overlay_2 is not changed + - overlay_3 is changed + + always: + - name: cleanup swarm + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/substring.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/substring.yml new file mode 100644 index 000000000..b4b37b272 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network/tasks/tests/substring.yml @@ -0,0 +1,41 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container and network names + set_fact: + nname_1: "{{ name_prefix ~ '-network-foo' }}" + nname_2: "{{ name_prefix ~ '-network-foobar' }}" +- name: Registering container and network names + set_fact: + dnetworks: "{{ dnetworks + [nname_1, nname_2] }}" + +#################################################################### + +- name: Create network (superstring) + docker_network: + name: "{{ nname_2 }}" + state: present + register: networks_1 + +- name: Create network (substring) + docker_network: + name: "{{ nname_1 }}" + state: present + register: networks_2 + +- name: Cleanup + docker_network: + name: "{{ network_name }}" + state: absent + loop: + - "{{ nname_1 }}" + - "{{ nname_2 }}" + loop_control: + loop_var: network_name + +- assert: + that: + - networks_1 is changed + - networks_2 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_network_info/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network_info/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network_info/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_network_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_network_info/tasks/main.yml new file mode 100644 index 000000000..910b4ec84 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_network_info/tasks/main.yml @@ -0,0 +1,80 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Create random network name + set_fact: + nname: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + + - name: Make sure network is not there + docker_network: + name: "{{ nname }}" + state: absent + force: true + + - name: Inspect a non-present network + docker_network_info: + name: "{{ nname }}" + register: result + + - assert: + that: + - "not result.exists" + - "'network' in result" + - "result.network is none" + + - name: Make sure network exists + docker_network: + name: "{{ nname }}" + state: present + + - name: Inspect a present network + docker_network_info: + name: "{{ nname }}" + register: result + - name: Dump docker_network_info result + debug: var=result + + - name: "Comparison: use 'docker network inspect'" + command: docker network inspect "{{ nname }}" + register: docker_inspect + ignore_errors: true + - block: + - set_fact: + docker_inspect_result: "{{ docker_inspect.stdout | from_json }}" + - name: Dump docker inspect result + debug: var=docker_inspect_result + when: docker_inspect is not failed + + - name: Cleanup + docker_network: + name: "{{ nname }}" + state: absent + force: true + + - assert: + that: + - result.exists + - "'network' in result" + - "result.network" + + - assert: + that: + - "result.network == docker_inspect_result[0]" + when: docker_inspect is not failed + - assert: + that: + - "'is too new. Maximum supported API version is' in docker_inspect.stderr" + when: docker_inspect is failed + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_network_info tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_node/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_node/aliases new file mode 100644 index 000000000..50e0e5f3a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_node/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/2 +destructive +needs/root diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_node/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_node/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_node/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_node/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_node/tasks/main.yml new file mode 100644 index 000000000..68bb5426c --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_node/tasks/main.yml @@ -0,0 +1,41 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Run the tests +- block: + - include_tasks: test_node.yml + + always: + - name: Cleanup (trying) + docker_swarm: + state: absent + force: true + diff: false + ignore_errors: true + + - name: Restart docker daemon + service: + name: docker + state: restarted + become: true + - name: Wait for docker daemon to be fully restarted + command: docker ps + ignore_errors: true + + - name: Cleanup + docker_swarm: + state: absent + force: true + diff: false + + when: docker_py_version is version('2.4.0', '>=') and docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_node tests!" + when: not(docker_py_version is version('2.4.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_node/tasks/test_node.yml b/ansible_collections/community/docker/tests/integration/targets/docker_node/tasks/test_node.yml new file mode 100644 index 000000000..89c9b3555 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_node/tasks/test_node.yml @@ -0,0 +1,844 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - name: Try to get docker_node_info when docker is not running in swarm mode + docker_node_info: + ignore_errors: true + register: output + + - name: assert failure when called when swarm is not in use or not run on manager node + assert: + that: + - 'output is failed' + - 'output.msg == "Error running docker swarm module: must run on swarm manager node"' + + - name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + register: output + + - name: assert changed when create a new swarm cluster + assert: + that: + - 'output is changed' + - 'output.actions[0] | regex_search("New Swarm cluster created: ")' + - 'output.swarm_facts.JoinTokens.Manager' + - 'output.swarm_facts.JoinTokens.Worker' + + - name: Try to get docker_node_info when docker is running in swarm mode and as manager + docker_node_info: + register: output + + - name: assert reading docker swarm node facts + assert: + that: + - 'output.nodes | length > 0' + - 'output.nodes[0].ID is string' + + - name: Register node ID + set_fact: + nodeid: "{{ output.nodes[0].ID }}" + +#################################################################### +## Set node as swarm manager ####################################### +#################################################################### + + - name: Try to set node as manager (check) + docker_node: + hostname: "{{ nodeid }}" + role: manager + check_mode: true + register: set_as_manager_1 + + - name: Try to set node as manager + docker_node: + hostname: "{{ nodeid }}" + role: manager + register: set_as_manager_2 + + - name: Try to set node as manager (idempotent) + docker_node: + hostname: "{{ nodeid }}" + role: manager + register: set_as_manager_3 + + - name: Try to set node as manager (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + role: manager + check_mode: true + register: set_as_manager_4 + + - name: assert that node role does has not changed + assert: + that: + - 'set_as_manager_1 is not changed' + - 'set_as_manager_2 is not changed' + - 'set_as_manager_3 is not changed' + - 'set_as_manager_4 is not changed' + - 'set_as_manager_1.node.Spec.Role == "manager"' + - 'set_as_manager_2.node.Spec.Role == "manager"' + - 'set_as_manager_3.node.Spec.Role == "manager"' + - 'set_as_manager_4.node.Spec.Role == "manager"' + +#################################################################### +## Set node as swarm worker ######################################## +#################################################################### + + - name: Try to set node as worker (check) + docker_node: + hostname: "{{ nodeid }}" + role: worker + check_mode: true + register: set_as_worker_1 + + - name: Try to set node as worker + docker_node: + hostname: "{{ nodeid }}" + role: worker + ignore_errors: true + register: set_as_worker_2 + + - name: assert that node cannot change role to worker + assert: + that: + - 'set_as_worker_1 is changed' + - 'set_as_worker_2 is failed' + - 'set_as_worker_2.msg | regex_search("attempting to demote the last manager of the swarm")' + +#################################################################### +## Set node as pasued ############################################## +#################################################################### + + - name: Try to set node availability as paused (check) + docker_node: + hostname: "{{ nodeid }}" + availability: pause + check_mode: true + register: set_as_paused_1 + + - name: Try to set node availability as paused + docker_node: + hostname: "{{ nodeid }}" + availability: pause + register: set_as_paused_2 + + - name: Try to set node availability as paused (idempotent) + docker_node: + hostname: "{{ nodeid }}" + availability: pause + register: set_as_paused_3 + + - name: Try to set node availability as paused (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + availability: pause + check_mode: true + register: set_as_paused_4 + + - name: assert node changed availability to paused + assert: + that: + - 'set_as_paused_1 is changed' + - 'set_as_paused_2 is changed' + - 'set_as_paused_3 is not changed' + - 'set_as_paused_4 is not changed' + - 'set_as_paused_2.node.Spec.Availability == "pause"' + +#################################################################### +## Set node as drained ############################################# +#################################################################### + + - name: Try to set node availability as drained (check) + docker_node: + hostname: "{{ nodeid }}" + availability: drain + check_mode: true + register: output_drain_1 + + - name: Try to set node availability as drained + docker_node: + hostname: "{{ nodeid }}" + availability: drain + register: output_drain_2 + + - name: Try to set node availability as drained (idempotent) + docker_node: + hostname: "{{ nodeid }}" + availability: drain + register: output_drain_3 + + - name: Try to set node availability as drained (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + availability: drain + check_mode: true + register: output_drain_4 + + - name: assert node changed availability to drained + assert: + that: + - 'output_drain_1 is changed' + - 'output_drain_2 is changed' + - 'output_drain_3 is not changed' + - 'output_drain_4 is not changed' + - 'output_drain_2.node.Spec.Availability == "drain"' + + +#################################################################### +## Set node as active ############################################## +#################################################################### + + - name: Try to set node availability as active (check) + docker_node: + hostname: "{{ nodeid }}" + availability: active + check_mode: true + register: output_active_1 + + - name: Try to set node availability as active + docker_node: + hostname: "{{ nodeid }}" + availability: active + register: output_active_2 + + - name: Try to set node availability as active (idempotent) + docker_node: + hostname: "{{ nodeid }}" + availability: active + register: output_active_3 + + - name: Try to set node availability as active (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + availability: active + check_mode: true + register: output_active_4 + + - name: assert node changed availability to active + assert: + that: + - 'output_active_1 is changed' + - 'output_active_2 is changed' + - 'output_active_3 is not changed' + - 'output_active_4 is not changed' + - 'output_active_2.node.Spec.Availability == "active"' + +#################################################################### +## Add single label ############################################### +#################################################################### + + - name: Try to add single label to swarm node (check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label1: value1 + check_mode: true + register: output_add_single_label_1 + + - name: Try to add single label to swarm node + docker_node: + hostname: "{{ nodeid }}" + labels: + label1: value1 + register: output_add_single_label_2 + + - name: Try to add single label to swarm node (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels: + label1: value1 + register: output_add_single_label_3 + + - name: Try to add single label to swarm node (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label1: value1 + check_mode: true + register: output_add_single_label_4 + + - name: assert adding single label to swarm node + assert: + that: + - 'output_add_single_label_1 is changed' + - 'output_add_single_label_2 is changed' + - 'output_add_single_label_3 is not changed' + - 'output_add_single_label_4 is not changed' + - 'output_add_single_label_2.node.Spec.Labels | length == 1' + - 'output_add_single_label_2.node.Spec.Labels.label1 == "value1"' + +#################################################################### +## Add multiple labels ############################################# +#################################################################### + + - name: Try to add five labels to swarm node (check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label2: value2 + label3: value3 + label4: value4 + label5: value5 + label6: value6 + check_mode: true + register: output_add_multiple_labels_1 + + - name: Try to add five labels to swarm node + docker_node: + hostname: "{{ nodeid }}" + labels: + label2: value2 + label3: value3 + label4: value4 + label5: value5 + label6: value6 + register: output_add_multiple_labels_2 + + - name: Try to add five labels to swarm node (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels: + label2: value2 + label3: value3 + label4: value4 + label5: value5 + label6: value6 + register: output_add_multiple_labels_3 + + - name: Try to add five labels to swarm node (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label2: value2 + label3: value3 + label4: value4 + label5: value5 + label6: value6 + check_mode: true + register: output_add_multiple_labels_4 + + - name: assert adding multiple labels to swarm node + assert: + that: + - 'output_add_multiple_labels_1 is changed' + - 'output_add_multiple_labels_2 is changed' + - 'output_add_multiple_labels_3 is not changed' + - 'output_add_multiple_labels_4 is not changed' + - 'output_add_multiple_labels_2.node.Spec.Labels | length == 6' + - 'output_add_multiple_labels_2.node.Spec.Labels.label1 == "value1"' + - 'output_add_multiple_labels_2.node.Spec.Labels.label6 == "value6"' + +#################################################################### +## Update label value ############################################## +#################################################################### + + - name: Update value of existing label (check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label1: value1111 + check_mode: true + register: output_update_label_1 + + - name: Update value of existing label + docker_node: + hostname: "{{ nodeid }}" + labels: + label1: value1111 + register: output_update_label_2 + + - name: Update value of existing label (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels: + label1: value1111 + register: output_update_label_3 + + - name: Update value of existing label (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label1: value1111 + check_mode: true + register: output_update_label_4 + + - name: assert updating single label assigned to swarm node + assert: + that: + - 'output_update_label_1 is changed' + - 'output_update_label_2 is changed' + - 'output_update_label_3 is not changed' + - 'output_update_label_4 is not changed' + - 'output_update_label_2.node.Spec.Labels | length == 6' + - 'output_update_label_2.node.Spec.Labels.label1 == "value1111"' + - 'output_update_label_2.node.Spec.Labels.label5 == "value5"' + +#################################################################### +## Update multiple labels values ################################### +#################################################################### + + - name: Update value of multiple existing label (check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label2: value2222 + label3: value3333 + check_mode: true + register: output_update_labels_1 + + - name: Update value of multiple existing label + docker_node: + hostname: "{{ nodeid }}" + labels: + label2: value2222 + label3: value3333 + register: output_update_labels_2 + + - name: Update value of multiple existing label (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels: + label2: value2222 + label3: value3333 + register: output_update_labels_3 + + - name: Update value of multiple existing label (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label2: value2222 + label3: value3333 + check_mode: true + register: output_update_labels_4 + + - name: assert updating multiple labels assigned to swarm node + assert: + that: + - 'output_update_labels_1 is changed' + - 'output_update_labels_2 is changed' + - 'output_update_labels_3 is not changed' + - 'output_update_labels_4 is not changed' + - 'output_update_labels_2.node.Spec.Labels | length == 6' + - 'output_update_labels_2.node.Spec.Labels.label1 == "value1111"' + - 'output_update_labels_2.node.Spec.Labels.label3 == "value3333"' + - 'output_update_labels_2.node.Spec.Labels.label5 == "value5"' + +#################################################################### +## Remove single label ############################################# +#################################################################### + + - name: Try to remove single existing label from swarm node (check) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label1 + check_mode: true + register: output_remove_label_1 + + - name: Try to remove single existing label from swarm node + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label1 + register: output_remove_label_2 + + - name: Try to remove single existing label from swarm node (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label1 + register: output_remove_label_3 + + - name: Try to remove single existing label from swarm node (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label1 + check_mode: true + register: output_remove_label_4 + + - name: assert removing single label from swarm node + assert: + that: + - 'output_remove_label_1 is changed' + - 'output_remove_label_2 is changed' + - 'output_remove_label_3 is not changed' + - 'output_remove_label_4 is not changed' + - 'output_remove_label_2.node.Spec.Labels | length == 5' + - '"label1" not in output_remove_label_2.node.Spec.Labels' + - 'output_remove_label_2.node.Spec.Labels.label3 == "value3333"' + - 'output_remove_label_2.node.Spec.Labels.label5 == "value5"' + + +#################################################################### +## Remove single not assigned to swarm label ####################### +#################################################################### + + - name: Try to remove single non-existing label from swarm node (check) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - labelnotexist + check_mode: true + register: output_remove_nonexist_label_1 + + - name: Try to remove single non-existing label from swarm node + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - labelnotexist + register: output_remove_nonexist_label_2 + + - name: Try to remove single non-existing label from swarm node (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - labelnotexist + register: output_remove_nonexist_label_3 + + - name: Try to remove single non-existing label from swarm node (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - labelnotexist + check_mode: true + register: output_remove_nonexist_label_4 + + - name: assert removing single non-existing label from swarm node + assert: + that: + - 'output_remove_nonexist_label_1 is not changed' + - 'output_remove_nonexist_label_2 is not changed' + - 'output_remove_nonexist_label_3 is not changed' + - 'output_remove_nonexist_label_4 is not changed' + - 'output_remove_nonexist_label_2.node.Spec.Labels | length == 5' + - '"label1" not in output_remove_nonexist_label_2.node.Spec.Labels' + - 'output_remove_nonexist_label_2.node.Spec.Labels.label3 == "value3333"' + - 'output_remove_nonexist_label_2.node.Spec.Labels.label5 == "value5"' + +#################################################################### +## Remove multiple labels ########################################## +#################################################################### + + - name: Try to remove two existing labels from swarm node (check) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label2 + - label3 + check_mode: true + register: output_remove_label_1 + + - name: Try to remove two existing labels from swarm node + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label2 + - label3 + register: output_remove_label_2 + + - name: Try to remove two existing labels from swarm node (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label2 + - label3 + register: output_remove_label_3 + + - name: Try to remove two existing labels from swarm node (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label2 + - label3 + check_mode: true + register: output_remove_label_4 + + - name: assert removing multiple labels from swarm node + assert: + that: + - 'output_remove_label_1 is changed' + - 'output_remove_label_2 is changed' + - 'output_remove_label_3 is not changed' + - 'output_remove_label_4 is not changed' + - 'output_remove_label_2.node.Spec.Labels | length == 3' + - '"label1" not in output_remove_label_2.node.Spec.Labels' + - '"label2" not in output_remove_label_2.node.Spec.Labels' + - 'output_remove_label_2.node.Spec.Labels.label5 == "value5"' + +#################################################################### +## Remove multiple labels, mix assigned and not assigned ########## +#################################################################### + + - name: Try to remove mix of existing amd non-existing labels from swarm node (check) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label4 + - labelisnotthere + check_mode: true + register: output_remove_mix_labels_1 + + - name: Try to remove mix of existing amd non-existing labels from swarm node + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label4 + - labelisnotthere + register: output_remove_mix_labels_2 + + - name: Try to remove mix of existing amd non-existing labels from swarm node (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label4 + - labelisnotthere + register: output_remove_mix_labels_3 + + - name: Try to remove mix of existing amd non-existing labels from swarm node (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels_to_remove: + - label4 + - labelisnotthere + check_mode: true + register: output_remove_mix_labels_4 + + - name: assert removing mix of existing and non-existing labels from swarm node + assert: + that: + - 'output_remove_mix_labels_1 is changed' + - 'output_remove_mix_labels_2 is changed' + - 'output_remove_mix_labels_3 is not changed' + - 'output_remove_mix_labels_4 is not changed' + - 'output_remove_mix_labels_2.node.Spec.Labels | length == 2' + - '"label1" not in output_remove_mix_labels_2.node.Spec.Labels' + - '"label4" not in output_remove_mix_labels_2.node.Spec.Labels' + - 'output_remove_mix_labels_2.node.Spec.Labels.label5 == "value5"' + +#################################################################### +## Add and remove labels ########################################### +#################################################################### + + - name: Try to add and remove nonoverlapping labels at the same time (check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label7: value7 + label8: value8 + labels_to_remove: + - label5 + check_mode: true + register: output_add_del_labels_1 + + - name: Try to add and remove nonoverlapping labels at the same time + docker_node: + hostname: "{{ nodeid }}" + labels: + label7: value7 + label8: value8 + labels_to_remove: + - label5 + register: output_add_del_labels_2 + + - name: Try to add and remove nonoverlapping labels at the same time (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels: + label7: value7 + label8: value8 + labels_to_remove: + - label5 + register: output_add_del_labels_3 + + - name: Try to add and remove nonoverlapping labels at the same time (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label7: value7 + label8: value8 + labels_to_remove: + - label5 + check_mode: true + register: output_add_del_labels_4 + + - name: assert adding and removing nonoverlapping labels from swarm node + assert: + that: + - 'output_add_del_labels_1 is changed' + - 'output_add_del_labels_2 is changed' + - 'output_add_del_labels_3 is not changed' + - 'output_add_del_labels_4 is not changed' + - 'output_add_del_labels_2.node.Spec.Labels | length == 3' + - '"label5" not in output_add_del_labels_2.node.Spec.Labels' + - 'output_add_del_labels_2.node.Spec.Labels.label8 == "value8"' + +#################################################################### +## Add and remove labels with label in both lists ################## +#################################################################### + + - name: Try to add or update and remove overlapping labels at the same time (check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label22: value22 + label6: value6666 + labels_to_remove: + - label6 + - label7 + check_mode: true + register: output_add_del_overlap_lables_1 + + - name: Try to add or update and remove overlapping labels at the same time + docker_node: + hostname: "{{ nodeid }}" + labels: + label22: value22 + label6: value6666 + labels_to_remove: + - label6 + - label7 + register: output_add_del_overlap_lables_2 + + - name: Try to add or update and remove overlapping labels at the same time (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels: + label22: value22 + label6: value6666 + labels_to_remove: + - label6 + - label7 + register: output_add_del_overlap_lables_3 + + - name: Try to add or update and remove overlapping labels at the same time (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label22: value22 + label6: value6666 + labels_to_remove: + - label6 + - label7 + check_mode: true + register: output_add_del_overlap_lables_4 + + - name: assert adding or updating and removing overlapping labels from swarm node + assert: + that: + - 'output_add_del_overlap_lables_1 is changed' + - 'output_add_del_overlap_lables_2 is changed' + - 'output_add_del_overlap_lables_3 is not changed' + - 'output_add_del_overlap_lables_4 is not changed' + - 'output_add_del_overlap_lables_2.node.Spec.Labels | length == 3' + - '"label7" not in output_add_del_overlap_lables_2.node.Spec.Labels' + - 'output_add_del_overlap_lables_2.node.Spec.Labels.label6 == "value6666"' + - 'output_add_del_overlap_lables_2.node.Spec.Labels.label22 == "value22"' + +#################################################################### +## Replace labels ############################################# +#################################################################### + + - name: Replace labels on swarm node (check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label11: value11 + label12: value12 + labels_state: replace + check_mode: true + register: output_replace_labels_1 + + - name: Replace labels on swarm node + docker_node: + hostname: "{{ nodeid }}" + labels: + label11: value11 + label12: value12 + labels_state: replace + register: output_replace_labels_2 + + - name: Replace labels on swarm node (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels: + label11: value11 + label12: value12 + labels_state: replace + register: output_replace_labels_3 + + - name: Replace labels on swarm node (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels: + label11: value11 + label12: value12 + labels_state: replace + check_mode: true + register: output_replace_labels_4 + + - name: assert replacing labels from swarm node + assert: + that: + - 'output_replace_labels_1 is changed' + - 'output_replace_labels_2 is changed' + - 'output_replace_labels_3 is not changed' + - 'output_replace_labels_4 is not changed' + - 'output_replace_labels_2.node.Spec.Labels | length == 2' + - '"label6" not in output_replace_labels_2.node.Spec.Labels' + - 'output_replace_labels_2.node.Spec.Labels.label12 == "value12"' + +#################################################################### +## Remove all labels ############################################# +#################################################################### + + - name: Remove all labels from swarm node (check) + docker_node: + hostname: "{{ nodeid }}" + labels_state: replace + check_mode: true + register: output_remove_labels_1 + + - name: Remove all labels from swarm node + docker_node: + hostname: "{{ nodeid }}" + labels_state: replace + register: output_remove_labels_2 + + - name: Remove all labels from swarm node (idempotent) + docker_node: + hostname: "{{ nodeid }}" + labels_state: replace + register: output_remove_labels_3 + + - name: Remove all labels from swarm node (idempotent check) + docker_node: + hostname: "{{ nodeid }}" + labels_state: replace + check_mode: true + register: output_remove_labels_4 + + - name: assert removing all lables from swarm node + assert: + that: + - 'output_remove_labels_1 is changed' + - 'output_remove_labels_2 is changed' + - 'output_remove_labels_3 is not changed' + - 'output_remove_labels_4 is not changed' + - 'output_remove_labels_2.node.Spec.Labels | length == 0' + + always: + - name: Cleanup + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_node_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_node_info/aliases new file mode 100644 index 000000000..9eec55e3a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_node_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/2 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_node_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_node_info/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_node_info/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_node_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_node_info/tasks/main.yml new file mode 100644 index 000000000..7d3a1b183 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_node_info/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_node_info.yml + when: docker_py_version is version('2.4.0', '>=') and docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_node_info tests!" + when: not(docker_py_version is version('2.4.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_node_info/tasks/test_node_info.yml b/ansible_collections/community/docker/tests/integration/targets/docker_node_info/tasks/test_node_info.yml new file mode 100644 index 000000000..9a89a2a17 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_node_info/tasks/test_node_info.yml @@ -0,0 +1,92 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - name: Try to get docker_node_info when docker is not running in swarm mode + docker_node_info: + ignore_errors: true + register: output + + - name: assert failure when called when swarm is not in use or not run on manager node + assert: + that: + - 'output is failed' + - 'output.msg == "Error running docker swarm module: must run on swarm manager node"' + + - name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + register: output + + - name: assert changed when create a new swarm cluster + assert: + that: + - 'output is changed' + - 'output.actions[0] | regex_search("New Swarm cluster created: ")' + - 'output.swarm_facts.JoinTokens.Manager' + - 'output.swarm_facts.JoinTokens.Worker' + + - name: Try to get docker_node_info when docker is running in swarm mode and as manager + docker_node_info: + register: output + + - name: assert reading docker swarm node facts + assert: + that: + - 'output.nodes | length > 0' + - 'output.nodes[0].ID is string' + + - name: Try to get docker_node_info using the self parameter + docker_node_info: + self: true + register: output + + - name: assert reading swarm facts with list of nodes option + assert: + that: + - 'output.nodes | length == 1' + - 'output.nodes[0].ID is string' + + - name: Get local docker node name + set_fact: + localnodename: "{{ output.nodes[0].Description.Hostname }}" + + + - name: Try to get docker_node_info using the local node name as parameter + docker_node_info: + name: "{{ localnodename }}" + register: output + + - name: assert reading reading swarm facts and using node filter (random node name) + assert: + that: + - 'output.nodes | length == 1' + - 'output.nodes[0].ID is string' + + - name: Create random name + set_fact: + randomnodename: "{{ 'node-%0x' % ((2**32) | random) }}" + + - name: Try to get docker_node_info using random node name as parameter + docker_node_info: + name: "{{ randomnodename }}" + register: output + + - name: assert reading reading swarm facts and using node filter (random node name) + assert: + that: + - 'output.nodes | length == 0' + + always: + - name: Cleanup + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_plugin/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_plugin/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/main.yml new file mode 100644 index 000000000..142614332 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/main.yml @@ -0,0 +1,34 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create random name prefix + set_fact: + name_prefix: "vieux/sshfs" + plugin_names: [] + +- debug: + msg: "Using name prefix {{ name_prefix }}" + +- name: Check whether /dev/fuse exists + stat: + path: /dev/fuse + register: dev_fuse_stat + +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: "Make sure plugin is removed" + docker_plugin: + plugin_name: "{{ item }}" + state: absent + with_items: "{{ plugin_names }}" + + when: docker_api_version is version('1.25', '>=') and dev_fuse_stat.stat.exists + +- fail: msg="Too old docker / docker-py version to run docker_plugin tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/tests/basic.yml b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/tests/basic.yml new file mode 100644 index 000000000..8ea6058ce --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/tests/basic.yml @@ -0,0 +1,192 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering plugin name + set_fact: + plugin_name: "{{ name_prefix }}" + +- name: Registering container name + set_fact: + plugin_names: "{{ plugin_names + [plugin_name] }}" + +############ basic test ############ +#################################### + +- name: Create a plugin (check mode) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: present + register: create_1_check + check_mode: true + +- name: Create a plugin + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: present + register: create_1 + +- name: Create a plugin (Idempotent, check mode) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: present + register: create_2_check + check_mode: true + +- name: Create a plugin (Idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: present + register: create_2 + +- name: Enable a plugin (check mode) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: enable + register: create_3_check + check_mode: true + +- name: Enable a plugin + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: enable + register: create_3 + +- name: Enable a plugin (Idempotent, check mode) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: enable + register: create_4_check + check_mode: true + +- name: Enable a plugin (Idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: enable + register: create_4 + +- name: Disable a plugin (check mode) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: disable + register: absent_1_check + check_mode: true + +- name: Disable a plugin + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: disable + register: absent_1 + +- name: Disable a plugin (Idempotent, check mode) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: disable + register: absent_2_check + check_mode: true + +- name: Disable a plugin (Idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: disable + register: absent_2 + +- name: Remove a plugin (check mode) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: absent + register: absent_3_check + check_mode: true + +- name: Remove a plugin + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: absent + register: absent_3 + +- name: Remove a plugin (Idempotent, check mode) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: absent + register: absent_4_check + check_mode: true + +- name: Remove a plugin (Idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: absent + register: absent_4 + +- name: Cleanup + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: absent + force_remove: true + +- assert: + that: + - create_1_check is changed + - create_1 is changed + - create_2_check is not changed + - create_2 is not changed + - create_3_check is changed + - create_3 is changed + - create_4_check is not changed + - create_4 is not changed + - absent_1_check is changed + - absent_1 is changed + - absent_2_check is not changed + - absent_2 is not changed + - absent_3_check is changed + - absent_3 is changed + - absent_4_check is not changed + - absent_4 is not changed + +############ Plugin_Options ############ +######################################## + +- name: Install a plugin with options + docker_plugin: + plugin_name: "{{ plugin_name }}" + plugin_options: + DEBUG: '1' + state: present + register: create_1 + +- name: Install a plugin with options (idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + plugin_options: + DEBUG: '1' + state: present + register: create_2 + +- name: Install a plugin with different options + docker_plugin: + plugin_name: "{{ plugin_name }}" + plugin_options: + DEBUG: '0' + state: present + register: update_1 + +- name: Install a plugin with different options (idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + plugin_options: + DEBUG: '0' + state: present + register: update_2 + +- name: Cleanup + docker_plugin: + plugin_name: "{{ plugin_name }}" + state: absent + force_remove: true + +- assert: + that: + - create_1 is changed + - create_2 is not changed + - update_1 is changed + - update_2 is not changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/tests/basic_with_alias.yml b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/tests/basic_with_alias.yml new file mode 100644 index 000000000..c26b188a0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_plugin/tasks/tests/basic_with_alias.yml @@ -0,0 +1,83 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Register plugin name and alias + set_fact: + plugin_name: "{{ name_prefix }}" + alias: "test" + +- name: Create a plugin with an alias + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: present + register: create_1 + +- name: Create a plugin with an alias (Idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: present + register: create_2 + +- name: Enable a plugin with an alias + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: enable + register: create_3 + +- name: Enable a plugin with an alias (Idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: enable + register: create_4 + +- name: Disable a plugin with an alias + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: disable + register: absent_1 + +- name: Disable a plugin with an alias (Idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: disable + register: absent_2 + +- name: Remove a plugin with an alias + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: absent + register: absent_3 + +- name: Remove a plugin with an alias (Idempotent) + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: absent + register: absent_4 + +- assert: + that: + - create_1 is changed + - create_2 is not changed + - create_3 is changed + - create_4 is not changed + - absent_1 is changed + - absent_2 is not changed + - absent_3 is changed + - absent_4 is not changed + +- name: Cleanup plugin with an alias + docker_plugin: + plugin_name: "{{ plugin_name }}" + alias: "{{ alias }}" + state: absent + force_remove: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_prune/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_prune/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_prune/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_prune/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_prune/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_prune/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_prune/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_prune/tasks/main.yml new file mode 100644 index 000000000..b2160ef0e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_prune/tasks/main.yml @@ -0,0 +1,153 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Create random names + set_fact: + cname: "{{ 'ansible-container-%0x' % ((2**32) | random) }}" + nname: "{{ 'ansible-network-%0x' % ((2**32) | random) }}" + vname: "{{ 'ansible-volume-%0x' % ((2**32) | random) }}" + +- block: + # Create objects to be pruned + - name: Create container (without volume) + docker_container: + name: "{{ cname }}" + image: "{{ docker_test_image_hello_world }}" + state: present + register: container + - name: Create network + docker_network: + name: "{{ nname }}" + state: present + register: network + - name: Create named volume + docker_volume: + name: "{{ vname }}" + state: present + register: volume + - name: Create anonymous volume + command: docker volume create + register: volume_anon + + - name: List volumes + command: docker volume list + + # Prune objects + - name: Prune everything + docker_prune: + containers: true + images: true + networks: true + volumes: true + builder_cache: true + register: result + + # Analyze result + - name: Show results + debug: + var: result + - name: General checks + assert: + that: + - result is changed + # containers + - container.container.Id in result.containers + - "'containers_space_reclaimed' in result" + # images + - "'images_space_reclaimed' in result" + # networks + - network.network.Name in result.networks + # volumes + - volume_anon.stdout in result.volumes + - "'volumes_space_reclaimed' in result" + # builder_cache + - "'builder_cache_space_reclaimed' in result" + - name: API-version specific volumes check (API version before 1.42) + assert: + that: + # For API version 1.41 and before, pruning always considers all volumes + - volume.volume.Name in result.volumes + when: docker_api_version is version('1.42', '<') + - name: API-version specific volumes check (API version 1.42+) + assert: + that: + # For API version 1.41 and before, pruning considers only anonymous volumes, + # so our named container is not removed + - volume.volume.Name not in result.volumes + when: docker_api_version is version('1.42', '>=') + + # Prune objects again + - name: Prune everything again (should have no change) + docker_prune: + containers: true + images: true + networks: true + volumes: true + builder_cache: true + register: result + + # Analyze result + - name: Show results + debug: + var: result + - name: General checks + assert: + that: + - result is not changed + # containers + - result.containers == [] + - result.containers_space_reclaimed == 0 + # images + - result.images == [] + - result.images_space_reclaimed == 0 + # networks + - result.networks == [] + # volumes + - result.volumes == [] + # builder_cache + - result.builder_cache_space_reclaimed == 0 + + # Test with filters + - name: Prune with filters + docker_prune: + images: true + images_filters: + dangling: true + register: result + + - name: Show results + debug: + var: result + + - name: Prune volumes with all filter (API version 1.42+) + when: docker_api_version is version('1.42', '>=') + block: + - name: Prune with filters + docker_prune: + volumes: true + volumes_filters: + all: true + register: result + + - name: Show results + debug: + var: result + + - name: Check results + assert: + that: + - result is changed + - volume.volume.Name in result.volumes + - "'volumes_space_reclaimed' in result" + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_prune tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_secret/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_secret/aliases new file mode 100644 index 000000000..fc581d544 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_secret/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/3 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_secret/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_secret/meta/main.yml new file mode 100644 index 000000000..2650229d8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_secret/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_secret/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_secret/tasks/main.yml new file mode 100644 index 000000000..291f6aa9a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_secret/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_secrets.yml + when: docker_py_version is version('2.1.0', '>=') and docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_secrets tests!" + when: not(docker_py_version is version('2.1.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_secret/tasks/test_secrets.yml b/ansible_collections/community/docker/tests/integration/targets/docker_secret/tasks/test_secrets.yml new file mode 100644 index 000000000..2615b6400 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_secret/tasks/test_secrets.yml @@ -0,0 +1,222 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}" + + - name: Parameter name should be required + docker_secret: + state: present + ignore_errors: true + register: output + + - name: assert failure when called with no name + assert: + that: + - 'output.failed' + - 'output.msg == "missing required arguments: name"' + + - name: Test parameters + docker_secret: + name: foo + state: present + ignore_errors: true + register: output + + - name: assert failure when called with no data + assert: + that: + - 'output.failed' + - 'output.msg == "state is present but any of the following are missing: data, data_src"' + + - name: Create secret + docker_secret: + name: db_password + data: opensesame! + state: present + register: output + + - name: Create variable secret_id + set_fact: + secret_id: "{{ output.secret_id }}" + + - name: Inspect secret + command: "docker secret inspect {{ secret_id }}" + register: inspect + ignore_errors: true + + - debug: var=inspect + + - name: assert secret creation succeeded + assert: + that: + - "'db_password' in inspect.stdout" + - "'ansible_key' in inspect.stdout" + when: inspect is not failed + - assert: + that: + - "'is too new. Maximum supported API version is' in inspect.stderr" + when: inspect is failed + + - name: Create secret again + docker_secret: + name: db_password + data: opensesame! + state: present + register: output + + - name: assert create secret is idempotent + assert: + that: + - not output.changed + + - name: Write secret into file + copy: + dest: "{{ remote_tmp_dir }}/data" + content: |- + opensesame! + + - name: Create secret again (from file) + docker_secret: + name: db_password + data_src: "{{ remote_tmp_dir }}/data" + state: present + register: output + + - name: assert create secret is idempotent + assert: + that: + - not output.changed + + - name: Create secret again (base64) + docker_secret: + name: db_password + data: b3BlbnNlc2FtZSE= + data_is_b64: true + state: present + register: output + + - name: assert create secret (base64) is idempotent + assert: + that: + - not output.changed + + - name: Update secret + docker_secret: + name: db_password + data: newpassword! + state: present + register: output + + - name: assert secret was updated + assert: + that: + - output.changed + - output.secret_id != secret_id + + - name: Remove secret + docker_secret: + name: db_password + state: absent + + - name: Check that secret is removed + command: "docker secret inspect {{ secret_id }}" + register: output + ignore_errors: true + + - name: assert secret was removed + assert: + that: + - output.failed + +# Rolling update + + - name: Create rolling secret + docker_secret: + name: rolling_password + data: opensesame! + rolling_versions: true + state: present + register: original_output + + - name: Create variable secret_id + set_fact: + secret_id: "{{ original_output.secret_id }}" + + - name: Inspect secret + command: "docker secret inspect {{ secret_id }}" + register: inspect + ignore_errors: true + + - debug: var=inspect + + - name: assert secret creation succeeded + assert: + that: + - "'rolling_password' in inspect.stdout" + - "'ansible_key' in inspect.stdout" + - "'ansible_version' in inspect.stdout" + - original_output.secret_name == 'rolling_password_v1' + when: inspect is not failed + - assert: + that: + - "'is too new. Maximum supported API version is' in inspect.stderr" + when: inspect is failed + + - name: Create secret again + docker_secret: + name: rolling_password + data: newpassword! + rolling_versions: true + state: present + register: new_output + + - name: assert that new version is created + assert: + that: + - new_output.changed + - new_output.secret_id != original_output.secret_id + - new_output.secret_name != original_output.secret_name + - new_output.secret_name == 'rolling_password_v2' + + - name: Remove rolling secrets + docker_secret: + name: rolling_password + rolling_versions: true + state: absent + + - name: Check that secret is removed + command: "docker secret inspect {{ original_output.secret_id }}" + register: output + ignore_errors: true + + - name: assert secret was removed + assert: + that: + - output.failed + + - name: Check that secret is removed + command: "docker secret inspect {{ new_output.secret_id }}" + register: output + ignore_errors: true + + - name: assert secret was removed + assert: + that: + - output.failed + + always: + - name: Remove Swarm cluster + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_stack/aliases new file mode 100644 index 000000000..9eec55e3a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/2 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack/meta/main.yml new file mode 100644 index 000000000..2650229d8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack/tasks/main.yml new file mode 100644 index 000000000..390e36ef4 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_stack.yml + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_stack tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack/tasks/test_stack.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack/tasks/test_stack.yml new file mode 100644 index 000000000..9f2d170e8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack/tasks/test_stack.yml @@ -0,0 +1,117 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}" + + - name: install docker_stack python requirements + pip: + name: jsondiff,pyyaml + + - name: Create a stack without name + register: output + docker_stack: + state: present + ignore_errors: true + + - name: assert failure when name not set + assert: + that: + - output is failed + - 'output.msg == "missing required arguments: name"' + + - name: Create a stack without compose + register: output + docker_stack: + name: test_stack + ignore_errors: true + + - name: assert failure when compose not set + assert: + that: + - output is failed + - 'output.msg == "compose parameter must be a list containing at least one element"' + + - name: Ensure stack is absent + register: output + docker_stack: + state: absent + name: test_stack + absent_retries: 30 + + - name: Template compose files + template: + src: "{{item}}" + dest: "{{remote_tmp_dir}}/" + with_items: + - stack_compose_base.yml + - stack_compose_overrides.yml + + - name: Create stack with compose file + register: output + docker_stack: + state: present + name: test_stack + compose: + - "{{remote_tmp_dir}}/stack_compose_base.yml" + + - name: assert test_stack changed on stack creation with compose file + assert: + that: + - output is changed + + # FIXME: updating the stack prevents leaving the swarm on Shippable + #- name: Update stack with YAML + # register: output + # docker_stack: + # state: present + # name: test_stack + # compose: + # - "{{stack_compose_base}}" + # - "{{stack_compose_overrides}}" + # + #- name: assert test_stack correctly changed on update with yaml + # assert: + # that: + # - output is changed + # - output.stack_spec_diff == stack_update_expected_diff + + - name: Delete stack + register: output + docker_stack: + state: absent + name: test_stack + absent_retries: 30 + + - name: assert delete of existing stack returns changed + assert: + that: + - output is changed + + - name: Delete stack again + register: output + docker_stack: + state: absent + name: test_stack + absent_retries: 30 + + - name: assert state=absent idempotency + assert: + that: + - output is not changed + + always: + - name: Remove a Swarm cluster + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack/templates/stack_compose_base.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack/templates/stack_compose_base.yml new file mode 100644 index 000000000..036033277 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack/templates/stack_compose_base.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +version: '3' +services: + busybox: + image: "{{ docker_test_image_busybox }}" + command: sleep 3600 diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack/templates/stack_compose_overrides.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack/templates/stack_compose_overrides.yml new file mode 100644 index 000000000..8743f1e98 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack/templates/stack_compose_overrides.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +version: '3' +services: + busybox: + environment: + envvar: value diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack/vars/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack/vars/main.yml new file mode 100644 index 000000000..a668012fc --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack/vars/main.yml @@ -0,0 +1,20 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +stack_compose_base: + version: '3' + services: + busybox: + image: "{{ docker_test_image_busybox }}" + command: sleep 3600 + +stack_compose_overrides: + version: '3' + services: + busybox: + environment: + envvar: value + +stack_update_expected_diff: '{"test_stack_busybox": {"TaskTemplate": {"ContainerSpec": {"Env": ["envvar=value"]}}}}' diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/aliases new file mode 100644 index 000000000..9eec55e3a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/2 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/meta/main.yml new file mode 100644 index 000000000..2650229d8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/tasks/main.yml new file mode 100644 index 000000000..c6cbb617a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_stack_info.yml + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_stack tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/tasks/test_stack_info.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/tasks/test_stack_info.yml new file mode 100644 index 000000000..58d6d5bb9 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/tasks/test_stack_info.yml @@ -0,0 +1,78 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - name: Get docker_stack_info when docker is not running in swarm mode + docker_stack_info: + ignore_errors: true + register: output + + - name: Assert failure when called when swarm is not running + assert: + that: + - 'output is failed' + - '"Error running docker stack" in output.msg' + + - name: Create a swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}" + + - name: Get docker_stack_info when docker is running and not stack available + docker_stack_info: + register: output + + - name: Assert stack facts + assert: + that: + - 'output.results | type_debug == "list"' + - 'output.results | length == 0' + + - name: Template compose files + template: + src: "{{ item }}" + dest: "{{ remote_tmp_dir }}/" + with_items: + - stack_compose_base.yml + - stack_compose_overrides.yml + + - name: Install docker_stack python requirements + pip: + name: jsondiff,pyyaml + + - name: Create stack with compose file + register: output + docker_stack: + state: present + name: test_stack + compose: + - "{{ remote_tmp_dir }}/stack_compose_base.yml" + + - name: Assert test_stack changed on stack creation with compose file + assert: + that: + - output is changed + + - name: Get docker_stack_info when docker is running + docker_stack_info: + register: output + + - name: assert stack facts + assert: + that: + - 'output.results | type_debug == "list"' + - 'output.results[0].Name == "test_stack"' + - 'output.results[0].Services == "1"' + + always: + - name: Cleanup + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/templates/stack_compose_base.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/templates/stack_compose_base.yml new file mode 100644 index 000000000..036033277 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/templates/stack_compose_base.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +version: '3' +services: + busybox: + image: "{{ docker_test_image_busybox }}" + command: sleep 3600 diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/templates/stack_compose_overrides.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/templates/stack_compose_overrides.yml new file mode 100644 index 000000000..8743f1e98 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/templates/stack_compose_overrides.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +version: '3' +services: + busybox: + environment: + envvar: value diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/vars/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/vars/main.yml new file mode 100644 index 000000000..a668012fc --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_info/vars/main.yml @@ -0,0 +1,20 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +stack_compose_base: + version: '3' + services: + busybox: + image: "{{ docker_test_image_busybox }}" + command: sleep 3600 + +stack_compose_overrides: + version: '3' + services: + busybox: + environment: + envvar: value + +stack_update_expected_diff: '{"test_stack_busybox": {"TaskTemplate": {"ContainerSpec": {"Env": ["envvar=value"]}}}}' diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/aliases new file mode 100644 index 000000000..9eec55e3a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/2 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/meta/main.yml new file mode 100644 index 000000000..2650229d8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/tasks/main.yml new file mode 100644 index 000000000..b52fa9c7d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_stack_task_info.yml + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_stack tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/tasks/test_stack_task_info.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/tasks/test_stack_task_info.yml new file mode 100644 index 000000000..30b5ca9e8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/tasks/test_stack_task_info.yml @@ -0,0 +1,88 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - name: Get docker_stack_info when docker is not running in swarm mode + docker_stack_info: + ignore_errors: true + register: output + + - name: Assert failure when called when swarm is not running + assert: + that: + - 'output is failed' + - '"Error running docker stack" in output.msg' + + - name: Create a swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}" + + - name: Get docker_stack_info when docker is running and not stack available + docker_stack_info: + register: output + + - name: Assert stack facts + assert: + that: + - 'output.results | type_debug == "list"' + - 'output.results | length == 0' + + - name: Template compose files + template: + src: "{{ item }}" + dest: "{{ remote_tmp_dir }}/" + with_items: + - stack_compose_base.yml + - stack_compose_overrides.yml + + - name: Install docker_stack python requirements + pip: + name: jsondiff,pyyaml + + - name: Create stack with compose file + register: output + docker_stack: + state: present + name: test_stack + compose: + - "{{ remote_tmp_dir }}/stack_compose_base.yml" + + - name: Assert test_stack changed on stack creation with compose file + assert: + that: + - output is changed + + - name: Wait a bit to make sure stack is running + pause: + seconds: 5 + + - name: Get docker_stack_info when docker is running + docker_stack_info: + register: output + + - name: Get docker_stack_task_info first element + docker_stack_task_info: + name: "{{ output.results[0].Name }}" + register: output + + - name: assert stack facts + assert: + that: + - 'output.results | type_debug == "list"' + - 'output.results[0].DesiredState == "Running"' + - 'output.results[0].Image == docker_test_image_busybox' + - 'output.results[0].Name == "test_stack_busybox.1"' + + always: + - name: Cleanup + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/templates/stack_compose_base.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/templates/stack_compose_base.yml new file mode 100644 index 000000000..036033277 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/templates/stack_compose_base.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +version: '3' +services: + busybox: + image: "{{ docker_test_image_busybox }}" + command: sleep 3600 diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/templates/stack_compose_overrides.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/templates/stack_compose_overrides.yml new file mode 100644 index 000000000..8743f1e98 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/templates/stack_compose_overrides.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +version: '3' +services: + busybox: + environment: + envvar: value diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/vars/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/vars/main.yml new file mode 100644 index 000000000..a668012fc --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_stack_task_info/vars/main.yml @@ -0,0 +1,20 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +stack_compose_base: + version: '3' + services: + busybox: + image: "{{ docker_test_image_busybox }}" + command: sleep 3600 + +stack_compose_overrides: + version: '3' + services: + busybox: + environment: + envvar: value + +stack_update_expected_diff: '{"test_stack_busybox": {"TaskTemplate": {"ContainerSpec": {"Env": ["envvar=value"]}}}}' diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/aliases new file mode 100644 index 000000000..19c65551b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/1 +destructive +needs/root diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/meta/main.yml new file mode 100644 index 000000000..e7ff3d68b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/meta/main.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_openssl + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/cleanup.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/cleanup.yml new file mode 100644 index 000000000..944e795c0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/cleanup.yml @@ -0,0 +1,38 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: CLEANUP | Leave Docker Swarm + docker_swarm: + state: absent + force: true + ignore_errors: true + register: leave_swarm + +- name: CLEANUP | Kill Docker and cleanup + when: leave_swarm is failed + block: + - name: CLEANUP | Kill docker daemon + command: systemctl kill -s 9 docker + become: true + + - name: CLEANUP | Clear out /var/lib/docker + shell: rm -rf /var/lib/docker/* + + - name: CLEANUP | Start docker daemon + service: + name: docker + state: started + become: true + + - name: CLEANUP | Wait for docker daemon to be fully started + command: docker ps + register: result + until: result is success + retries: 10 + + - name: CLEANUP | Leave Docker Swarm + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/main.yml new file mode 100644 index 000000000..16f681530 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/main.yml @@ -0,0 +1,28 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Run Docker Swarm tests + when: + - docker_py_version is version('1.10.0', '>=') + - docker_api_version is version('1.25', '>=') + + block: + - include_tasks: "{{ item }}" + with_fileglob: + - 'tests/*.yml' + + always: + - import_tasks: cleanup.yml + +- fail: + msg: "Too old docker / docker-py version to run docker_swarm tests!" + when: + - not(docker_py_version is version('1.10.0', '>=') and docker_api_version is version('1.25', '>=')) + - (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/run-test.yml new file mode 100644 index 000000000..f55df21f8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/run-test.yml @@ -0,0 +1,4 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/basic.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/basic.yml new file mode 100644 index 000000000..79d524e5b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/basic.yml @@ -0,0 +1,163 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- debug: + msg: Running tests/basic.yml + +#################################################################### +## Errors ########################################################## +#################################################################### +- name: Test parameters with state=join + docker_swarm: + state: join + ignore_errors: true + register: output + +- name: assert failure when called with state=join and no remote_addrs,join_token + assert: + that: + - 'output.failed' + - 'output.msg == "state is join but all of the following are missing: remote_addrs, join_token"' + +- name: Test parameters with state=remove + docker_swarm: + state: remove + ignore_errors: true + register: output + +- name: assert failure when called with state=remove and no node_id + assert: + that: + - 'output.failed' + - 'output.msg == "state is remove but all of the following are missing: node_id"' + +#################################################################### +## Creation ######################################################## +#################################################################### + +- name: Create a Swarm cluster (check mode) + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + check_mode: true + diff: true + register: output_1 + +- name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + diff: true + register: output_2 + +- name: Create a Swarm cluster (idempotent) + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + diff: true + register: output_3 + +- name: Create a Swarm cluster (idempotent, check mode) + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + check_mode: true + diff: true + register: output_4 + +- name: Create a Swarm cluster (force re-create) + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + force: true + diff: true + register: output_5 + +- name: Create a Swarm cluster (force re-create, check mode) + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + force: true + check_mode: true + diff: true + register: output_6 + +- name: assert changed when create a new swarm cluster + assert: + that: + - 'output_1 is changed' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] | regex_search("New Swarm cluster created: ")' + - 'output_2.swarm_facts.JoinTokens.Manager' + - 'output_2.swarm_facts.JoinTokens.Worker' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +#################################################################### +## Removal ######################################################### +#################################################################### + +- name: Remove a Swarm cluster (check mode) + docker_swarm: + state: absent + force: true + check_mode: true + diff: true + register: output_1 + +- name: Remove a Swarm cluster + docker_swarm: + state: absent + force: true + diff: true + register: output_2 + +- name: Remove a Swarm cluster (idempotent) + docker_swarm: + state: absent + force: true + diff: true + register: output_3 + +- name: Remove a Swarm cluster (idempotent, check mode) + docker_swarm: + state: absent + force: true + check_mode: true + diff: true + register: output_4 + +- name: assert changed when remove a swarm cluster + assert: + that: + - 'output_1 is changed' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Node has left the swarm cluster"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + +- include_tasks: cleanup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/options-ca.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/options-ca.yml new file mode 100644 index 000000000..86661ecb0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/options-ca.yml @@ -0,0 +1,133 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- debug: + msg: Running tests/options-ca.yml +- name: options-ca + when: cryptography_version.stdout is version('1.6', '>=') + block: + - name: Generate privatekey + loop: + - key1 + - key2 + loop_control: + loop_var: key + community.crypto.openssl_privatekey: + path: '{{ remote_tmp_dir }}/ansible_{{ key }}.key' + size: 2048 + mode: '0666' + - name: Generate CSR + loop: + - key1 + - key2 + loop_control: + loop_var: key + community.crypto.openssl_csr: + path: '{{ remote_tmp_dir }}/ansible_{{ key }}.csr' + privatekey_path: '{{ remote_tmp_dir }}/ansible_{{ key }}.key' + basic_constraints: + - CA:TRUE + key_usage: + - keyCertSign + - name: Generate self-signed certificate + loop: + - key1 + - key2 + loop_control: + loop_var: key + community.crypto.x509_certificate: + path: '{{ remote_tmp_dir }}/ansible_{{ key }}.pem' + privatekey_path: '{{ remote_tmp_dir }}/ansible_{{ key }}.key' + csr_path: '{{ remote_tmp_dir }}/ansible_{{ key }}.csr' + provider: selfsigned + - name: Load certificates + slurp: + src: '{{ remote_tmp_dir }}/{{ item }}' + loop: + - ansible_key1.pem + - ansible_key2.pem + register: ansible_certificates + - name: Load certificate keys + slurp: + src: '{{ remote_tmp_dir }}/{{ item }}' + loop: + - ansible_key1.key + - ansible_key2.key + register: ansible_keys + - name: signing_ca_cert and signing_ca_key (check mode) + docker_swarm: + advertise_addr: '{{ansible_default_ipv4.address | default("127.0.0.1")}}' + state: present + signing_ca_cert: '{{ ansible_certificates.results[0].content | b64decode }}' + signing_ca_key: '{{ ansible_keys.results[0].content | b64decode }}' + timeout: 120 + check_mode: true + diff: true + register: output_1 + ignore_errors: true + - name: signing_ca_cert and signing_ca_key + docker_swarm: + advertise_addr: '{{ansible_default_ipv4.address | default("127.0.0.1")}}' + state: present + signing_ca_cert: '{{ ansible_certificates.results[0].content | b64decode }}' + signing_ca_key: '{{ ansible_keys.results[0].content | b64decode }}' + timeout: 120 + diff: true + register: output_2 + ignore_errors: true + - name: Private key + debug: msg="{{ ansible_keys.results[0].content | b64decode }}" + - name: Cert + debug: msg="{{ ansible_certificates.results[0].content | b64decode }}" + - docker_swarm_info: null + register: output + ignore_errors: true + - debug: var=output + - name: signing_ca_cert and signing_ca_key (change, check mode) + docker_swarm: + state: present + signing_ca_cert: '{{ ansible_certificates.results[1].content | b64decode }}' + signing_ca_key: '{{ ansible_keys.results[1].content | b64decode }}' + timeout: 120 + check_mode: true + diff: true + register: output_5 + ignore_errors: true + - name: signing_ca_cert and signing_ca_key (change) + docker_swarm: + state: present + signing_ca_cert: '{{ ansible_certificates.results[1].content | b64decode }}' + signing_ca_key: '{{ ansible_keys.results[1].content | b64decode }}' + timeout: 120 + diff: true + register: output_6 + ignore_errors: true + - name: assert signing_ca_cert and signing_ca_key + assert: + that: + - output_1 is changed + - 'output_1.actions[0] | regex_search("New Swarm cluster created: ")' + - output_1.diff.before is defined + - output_1.diff.after is defined + - output_2 is changed + - 'output_2.actions[0] | regex_search("New Swarm cluster created: ")' + - output_2.diff.before is defined + - output_2.diff.after is defined + - output_5 is changed + - output_5.actions[0] == "Swarm cluster updated" + - output_5.diff.before is defined + - output_5.diff.after is defined + - output_6 is changed + - output_6.actions[0] == "Swarm cluster updated" + - output_6.diff.before is defined + - output_6.diff.after is defined + when: docker_py_version is version('2.6.0', '>=') + - assert: + that: + - output_1 is failed + - ('version is ' ~ docker_py_version ~ ' ') in output_1.msg + - '"Minimum version required is 2.6.0 " in output_1.msg' + when: docker_py_version is version('2.6.0', '<') + - include_tasks: cleanup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/options.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/options.yml new file mode 100644 index 000000000..f88aa3f40 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/options.yml @@ -0,0 +1,1163 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- debug: + msg: Running tests/options.yml + +- name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + name: default + diff: true + +#################################################################### +## autolock_managers ############################################### +#################################################################### + +- name: autolock_managers (check mode) + docker_swarm: + state: present + autolock_managers: true + check_mode: true + diff: true + register: output_1 + ignore_errors: true + +- name: autolock_managers + docker_swarm: + state: present + autolock_managers: true + diff: true + register: output_2 + ignore_errors: true + +- name: autolock_managers (idempotent) + docker_swarm: + state: present + autolock_managers: true + diff: true + register: output_3 + ignore_errors: true + +- name: autolock_managers (idempotent, check mode) + docker_swarm: + state: present + autolock_managers: true + check_mode: true + diff: true + register: output_4 + ignore_errors: true + +- name: autolock_managers (change, check mode) + docker_swarm: + state: present + autolock_managers: false + check_mode: true + diff: true + register: output_5 + ignore_errors: true + +- name: autolock_managers (change) + docker_swarm: + state: present + autolock_managers: false + diff: true + register: output_6 + ignore_errors: true + +- name: autolock_managers (force new swarm) + docker_swarm: + state: present + force: true + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + autolock_managers: true + diff: true + register: output_7 + ignore_errors: true + +- name: assert autolock_managers changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + when: docker_py_version is version('2.6.0', '>=') + +- name: assert UnlockKey in swarm_facts + assert: + that: + - 'output_2.swarm_facts.UnlockKey' + - 'output_3.swarm_facts.UnlockKey is none' + - 'output_6.swarm_facts.UnlockKey is none' + - 'output_7.swarm_facts.UnlockKey' + when: docker_py_version is version('2.7.0', '>=') + +- assert: + that: + - output_1 is failed + - "('version is ' ~ docker_py_version ~ ' ') in output_1.msg" + - "'Minimum version required is 2.6.0 ' in output_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## ca_force_rotate ################################################# +#################################################################### + +- name: ca_force_rotate (check mode) + docker_swarm: + state: present + ca_force_rotate: 1 + check_mode: true + diff: true + register: output_1 + ignore_errors: true + +- name: ca_force_rotate + docker_swarm: + state: present + ca_force_rotate: 1 + diff: true + register: output_2 + ignore_errors: true + +- name: ca_force_rotate (idempotent) + docker_swarm: + state: present + ca_force_rotate: 1 + diff: true + register: output_3 + ignore_errors: true + +- name: ca_force_rotate (idempotent, check mode) + docker_swarm: + state: present + ca_force_rotate: 1 + check_mode: true + diff: true + register: output_4 + ignore_errors: true + +- name: ca_force_rotate (change, check mode) + docker_swarm: + state: present + ca_force_rotate: 0 + check_mode: true + diff: true + register: output_5 + ignore_errors: true + +- name: ca_force_rotate (change) + docker_swarm: + state: present + ca_force_rotate: 0 + diff: true + register: output_6 + ignore_errors: true + +- name: assert ca_force_rotate changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - output_1 is failed + - "('version is ' ~ docker_py_version ~ ' ') in output_1.msg" + - "'Minimum version required is 2.6.0 ' in output_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## dispatcher_heartbeat_period ##################################### +#################################################################### + +- name: dispatcher_heartbeat_period (check mode) + docker_swarm: + state: present + dispatcher_heartbeat_period: 10 + check_mode: true + diff: true + register: output_1 + +- name: dispatcher_heartbeat_period + docker_swarm: + state: present + dispatcher_heartbeat_period: 10 + diff: true + register: output_2 + +- name: dispatcher_heartbeat_period (idempotent) + docker_swarm: + state: present + dispatcher_heartbeat_period: 10 + diff: true + register: output_3 + +- name: dispatcher_heartbeat_period (idempotent, check mode) + docker_swarm: + state: present + dispatcher_heartbeat_period: 10 + check_mode: true + diff: true + register: output_4 + +- name: dispatcher_heartbeat_period (change, check mode) + docker_swarm: + state: present + dispatcher_heartbeat_period: 23 + check_mode: true + diff: true + register: output_5 + +- name: dispatcher_heartbeat_period (change) + docker_swarm: + state: present + dispatcher_heartbeat_period: 23 + diff: true + register: output_6 + +- name: assert dispatcher_heartbeat_period changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +#################################################################### +## election_tick ################################################### +#################################################################### + +- name: election_tick (check mode) + docker_swarm: + state: present + election_tick: 20 + check_mode: true + diff: true + register: output_1 + +- name: election_tick + docker_swarm: + state: present + election_tick: 20 + diff: true + register: output_2 + +- name: election_tick (idempotent) + docker_swarm: + state: present + election_tick: 20 + diff: true + register: output_3 + +- name: election_tick (idempotent, check mode) + docker_swarm: + state: present + election_tick: 20 + check_mode: true + diff: true + register: output_4 + +- name: election_tick (change, check mode) + docker_swarm: + state: present + election_tick: 5 + check_mode: true + diff: true + register: output_5 + +- name: election_tick (change) + docker_swarm: + state: present + election_tick: 5 + diff: true + register: output_6 + +- name: assert election_tick changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +#################################################################### +## heartbeat_tick ################################################## +#################################################################### + +- name: heartbeat_tick (check mode) + docker_swarm: + state: present + heartbeat_tick: 2 + check_mode: true + diff: true + register: output_1 + +- name: heartbeat_tick + docker_swarm: + state: present + heartbeat_tick: 2 + diff: true + register: output_2 + +- name: heartbeat_tick (idempotent) + docker_swarm: + state: present + heartbeat_tick: 2 + diff: true + register: output_3 + +- name: heartbeat_tick (idempotent, check mode) + docker_swarm: + state: present + heartbeat_tick: 2 + check_mode: true + diff: true + register: output_4 + +- name: heartbeat_tick (change, check mode) + docker_swarm: + state: present + heartbeat_tick: 3 + check_mode: true + diff: true + register: output_5 + +- name: heartbeat_tick (change) + docker_swarm: + state: present + heartbeat_tick: 3 + diff: true + register: output_6 + +- name: assert heartbeat_tick changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +#################################################################### +## keep_old_snapshots ############################################## +#################################################################### +- name: keep_old_snapshots (check mode) + docker_swarm: + state: present + keep_old_snapshots: 1 + check_mode: true + diff: true + register: output_1 + +- name: keep_old_snapshots + docker_swarm: + state: present + keep_old_snapshots: 1 + diff: true + register: output_2 + +- name: keep_old_snapshots (idempotent) + docker_swarm: + state: present + keep_old_snapshots: 1 + diff: true + register: output_3 + +- name: keep_old_snapshots (idempotent, check mode) + docker_swarm: + state: present + keep_old_snapshots: 1 + check_mode: true + diff: true + register: output_4 + +- name: keep_old_snapshots (change, check mode) + docker_swarm: + state: present + keep_old_snapshots: 2 + check_mode: true + diff: true + register: output_5 + +- name: keep_old_snapshots (change) + docker_swarm: + state: present + keep_old_snapshots: 2 + diff: true + register: output_6 + +- name: assert keep_old_snapshots changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +#################################################################### +## labels ########################################################## +#################################################################### +- name: labels (check mode) + docker_swarm: + state: present + labels: + a: v1 + b: v2 + check_mode: true + diff: true + register: output_1 + ignore_errors: true + +- name: labels + docker_swarm: + state: present + labels: + a: v1 + b: v2 + diff: true + register: output_2 + ignore_errors: true + +- name: labels (idempotent) + docker_swarm: + state: present + labels: + a: v1 + b: v2 + diff: true + register: output_3 + ignore_errors: true + +- name: labels (idempotent, check mode) + docker_swarm: + state: present + labels: + a: v1 + b: v2 + check_mode: true + diff: true + register: output_4 + ignore_errors: true + +- name: labels (change, check mode) + docker_swarm: + state: present + labels: + a: v1 + c: v3 + check_mode: true + diff: true + register: output_5 + ignore_errors: true + +- name: labels (change) + docker_swarm: + state: present + labels: + a: v1 + c: v3 + diff: true + register: output_6 + ignore_errors: true + +- name: labels (not specifying, check mode) + docker_swarm: + state: present + check_mode: true + diff: true + register: output_7 + ignore_errors: true + +- name: labels (not specifying) + docker_swarm: + state: present + diff: true + register: output_8 + ignore_errors: true + +- name: labels (idempotency, check that labels are still there) + docker_swarm: + state: present + labels: + a: v1 + c: v3 + diff: true + register: output_9 + ignore_errors: true + +- name: labels (empty, check mode) + docker_swarm: + state: present + labels: {} + check_mode: true + diff: true + register: output_10 + ignore_errors: true + +- name: labels (empty) + docker_swarm: + state: present + labels: {} + diff: true + register: output_11 + ignore_errors: true + +- name: labels (empty, idempotent, check mode) + docker_swarm: + state: present + labels: {} + check_mode: true + diff: true + register: output_12 + ignore_errors: true + +- name: labels (empty, idempotent) + docker_swarm: + state: present + labels: {} + diff: true + register: output_13 + ignore_errors: true + +- name: assert labels changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + - 'output_7 is not changed' + - 'output_7.actions[0] == "No modification"' + - 'output_7.diff.before is defined' + - 'output_7.diff.after is defined' + - 'output_8 is not changed' + - 'output_8.actions[0] == "No modification"' + - 'output_8.diff.before is defined' + - 'output_8.diff.after is defined' + - 'output_9 is not changed' + - 'output_9.actions[0] == "No modification"' + - 'output_9.diff.before is defined' + - 'output_9.diff.after is defined' + - 'output_10 is changed' + - 'output_10.actions[0] == "Swarm cluster updated"' + - 'output_10.diff.before is defined' + - 'output_10.diff.after is defined' + - 'output_11 is changed' + - 'output_11.actions[0] == "Swarm cluster updated"' + - 'output_11.diff.before is defined' + - 'output_11.diff.after is defined' + - 'output_12 is not changed' + - 'output_12.actions[0] == "No modification"' + - 'output_12.diff.before is defined' + - 'output_12.diff.after is defined' + - 'output_13 is not changed' + - 'output_13.actions[0] == "No modification"' + - 'output_13.diff.before is defined' + - 'output_13.diff.after is defined' + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - output_1 is failed + - "('version is ' ~ docker_py_version ~ ' ') in output_1.msg" + - "'Minimum version required is 2.6.0 ' in output_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## log_entries_for_slow_followers ################################## +#################################################################### +- name: log_entries_for_slow_followers (check mode) + docker_swarm: + state: present + log_entries_for_slow_followers: 42 + check_mode: true + diff: true + register: output_1 + +- name: log_entries_for_slow_followers + docker_swarm: + state: present + log_entries_for_slow_followers: 42 + diff: true + register: output_2 + +- name: log_entries_for_slow_followers (idempotent) + docker_swarm: + state: present + log_entries_for_slow_followers: 42 + diff: true + register: output_3 + +- name: log_entries_for_slow_followers (idempotent, check mode) + docker_swarm: + state: present + log_entries_for_slow_followers: 42 + check_mode: true + diff: true + register: output_4 + +- name: log_entries_for_slow_followers (change, check mode) + docker_swarm: + state: present + log_entries_for_slow_followers: 23 + check_mode: true + diff: true + register: output_5 + +- name: log_entries_for_slow_followers (change) + docker_swarm: + state: present + log_entries_for_slow_followers: 23 + diff: true + register: output_6 + +- name: assert log_entries_for_slow_followers changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +#################################################################### +## name ############################################################ +#################################################################### +- name: name (idempotent, check mode) + docker_swarm: + state: present + name: default + check_mode: true + diff: true + register: output_1 + +- name: name (idempotent) + docker_swarm: + state: present + name: default + diff: true + register: output_2 + +# The name 'default' is hardcoded in docker swarm. Trying to change +# it causes a failure. This might change in the future, so we also +# accept a change for this test. +- name: name (change, should fail) + docker_swarm: + state: present + name: foobar + diff: true + register: output_3 + ignore_errors: true + +- name: assert name changes + assert: + that: + - 'output_1 is not changed' + - 'output_1.actions[0] == "No modification"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is not changed' + - 'output_2.actions[0] == "No modification"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is failed or output_3 is changed' + +#################################################################### +## node_cert_expiry ################################################ +#################################################################### +- name: node_cert_expiry (check mode) + docker_swarm: + state: present + node_cert_expiry: 7896000000000000 + check_mode: true + diff: true + register: output_1 + +- name: node_cert_expiry + docker_swarm: + state: present + node_cert_expiry: 7896000000000000 + diff: true + register: output_2 + +- name: node_cert_expiry (idempotent) + docker_swarm: + state: present + node_cert_expiry: 7896000000000000 + diff: true + register: output_3 + +- name: node_cert_expiry (idempotent, check mode) + docker_swarm: + state: present + node_cert_expiry: 7896000000000000 + check_mode: true + diff: true + register: output_4 + +- name: node_cert_expiry (change, check mode) + docker_swarm: + state: present + node_cert_expiry: 8766000000000000 + check_mode: true + diff: true + register: output_5 + +- name: node_cert_expiry (change) + docker_swarm: + state: present + node_cert_expiry: 8766000000000000 + diff: true + register: output_6 + +- name: assert node_cert_expiry changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +#################################################################### +## rotate_manager_token ############################################ +#################################################################### +- name: rotate_manager_token (true, check mode) + docker_swarm: + state: present + rotate_manager_token: true + check_mode: true + diff: true + register: output_1 + +- name: rotate_manager_token (true) + docker_swarm: + state: present + rotate_manager_token: true + diff: true + register: output_2 + +- name: rotate_manager_token (false, idempotent) + docker_swarm: + state: present + rotate_manager_token: false + diff: true + register: output_3 + +- name: rotate_manager_token (false, check mode) + docker_swarm: + state: present + rotate_manager_token: false + check_mode: true + diff: true + register: output_4 + +- name: assert rotate_manager_token changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + +#################################################################### +## rotate_worker_token ############################################# +#################################################################### +- name: rotate_worker_token (true, check mode) + docker_swarm: + state: present + rotate_worker_token: true + check_mode: true + diff: true + register: output_1 + +- name: rotate_worker_token (true) + docker_swarm: + state: present + rotate_worker_token: true + diff: true + register: output_2 + +- name: rotate_worker_token (false, idempotent) + docker_swarm: + state: present + rotate_worker_token: false + diff: true + register: output_3 + +- name: rotate_worker_token (false, check mode) + docker_swarm: + state: present + rotate_worker_token: false + check_mode: true + diff: true + register: output_4 + +- name: assert rotate_worker_token changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + +#################################################################### +## snapshot_interval ############################################### +#################################################################### +- name: snapshot_interval (check mode) + docker_swarm: + state: present + snapshot_interval: 12345 + check_mode: true + diff: true + register: output_1 + +- name: snapshot_interval + docker_swarm: + state: present + snapshot_interval: 12345 + diff: true + register: output_2 + +- name: snapshot_interval (idempotent) + docker_swarm: + state: present + snapshot_interval: 12345 + diff: true + register: output_3 + +- name: snapshot_interval (idempotent, check mode) + docker_swarm: + state: present + snapshot_interval: 12345 + check_mode: true + diff: true + register: output_4 + +- name: snapshot_interval (change, check mode) + docker_swarm: + state: present + snapshot_interval: 54321 + check_mode: true + diff: true + register: output_5 + +- name: snapshot_interval (change) + docker_swarm: + state: present + snapshot_interval: 54321 + diff: true + register: output_6 + +- name: assert snapshot_interval changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +#################################################################### +## task_history_retention_limit #################################### +#################################################################### +- name: task_history_retention_limit (check mode) + docker_swarm: + state: present + task_history_retention_limit: 23 + check_mode: true + diff: true + register: output_1 + +- name: task_history_retention_limit + docker_swarm: + state: present + task_history_retention_limit: 23 + diff: true + register: output_2 + +- name: task_history_retention_limit (idempotent) + docker_swarm: + state: present + task_history_retention_limit: 23 + diff: true + register: output_3 + +- name: task_history_retention_limit (idempotent, check mode) + docker_swarm: + state: present + task_history_retention_limit: 23 + check_mode: true + diff: true + register: output_4 + +- name: task_history_retention_limit (change, check mode) + docker_swarm: + state: present + task_history_retention_limit: 7 + check_mode: true + diff: true + register: output_5 + +- name: task_history_retention_limit (change) + docker_swarm: + state: present + task_history_retention_limit: 7 + diff: true + register: output_6 + +- name: assert task_history_retention_limit changes + assert: + that: + - 'output_1 is changed' + - 'output_1.actions[0] == "Swarm cluster updated"' + - 'output_1.diff.before is defined' + - 'output_1.diff.after is defined' + - 'output_2 is changed' + - 'output_2.actions[0] == "Swarm cluster updated"' + - 'output_2.diff.before is defined' + - 'output_2.diff.after is defined' + - 'output_3 is not changed' + - 'output_3.actions[0] == "No modification"' + - 'output_3.diff.before is defined' + - 'output_3.diff.after is defined' + - 'output_4 is not changed' + - 'output_4.actions[0] == "No modification"' + - 'output_4.diff.before is defined' + - 'output_4.diff.after is defined' + - 'output_5 is changed' + - 'output_5.actions[0] == "Swarm cluster updated"' + - 'output_5.diff.before is defined' + - 'output_5.diff.after is defined' + - 'output_6 is changed' + - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_6.diff.before is defined' + - 'output_6.diff.after is defined' + +- include_tasks: cleanup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/remote-addr-pool.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/remote-addr-pool.yml new file mode 100644 index 000000000..66f422e55 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm/tasks/tests/remote-addr-pool.yml @@ -0,0 +1,95 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- debug: + msg: Running tests/remote-addr-pool.yml + +#################################################################### +## default_addr_pool ############################################### +#################################################################### + +- name: default_addr_pool + docker_swarm: + state: present + default_addr_pool: + - "2.0.0.0/16" + diff: true + register: output_1 + ignore_errors: true + +- name: default_addr_pool (idempotent) + docker_swarm: + state: present + default_addr_pool: + - "2.0.0.0/16" + diff: true + register: output_2 + ignore_errors: true + +- name: assert default_addr_pool + assert: + that: + - 'output_1 is changed' + - 'output_2 is not changed' + - 'output_2.swarm_facts.DefaultAddrPool == ["2.0.0.0/16"]' + when: + - docker_api_version is version('1.39', '>=') + - docker_py_version is version('4.0.0', '>=') + +- name: assert default_addr_pool failed when unsupported + assert: + that: + - 'output_1 is failed' + - "'Minimum version required' in output_1.msg" + when: docker_api_version is version('1.39', '<') or + docker_py_version is version('4.0.0', '<') + +#################################################################### +## subnet_size ##################################################### +#################################################################### +- name: Leave swarm + docker_swarm: + state: absent + force: true + default_addr_pool: + - "2.0.0.0/16" + diff: true + +- name: subnet_size + docker_swarm: + state: present + force: true + subnet_size: 26 + diff: true + register: output_1 + ignore_errors: true + +- name: subnet_size (idempotent) + docker_swarm: + state: present + subnet_size: 26 + diff: true + register: output_2 + ignore_errors: true + +- name: assert subnet_size + assert: + that: + - 'output_1 is changed' + - 'output_2 is not changed' + - 'output_2.swarm_facts.SubnetSize == 26' + when: + - docker_api_version is version('1.39', '>=') + - docker_py_version is version('4.0.0', '>=') + +- name: assert subnet_size failed when unsupported + assert: + that: + - output_1 is failed + - "'Minimum version required' in output_1.msg" + when: docker_api_version is version('1.39', '<') or + docker_py_version is version('4.0.0', '<') + +- include_tasks: cleanup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/aliases new file mode 100644 index 000000000..6f61c620d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/1 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/tasks/main.yml new file mode 100644 index 000000000..b24184dbe --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_swarm_info.yml + when: docker_py_version is version('1.10.0', '>=') and docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_swarm_info tests!" + when: not(docker_py_version is version('1.10.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/tasks/test_swarm_info.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/tasks/test_swarm_info.yml new file mode 100644 index 000000000..288e2a0b0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_info/tasks/test_swarm_info.yml @@ -0,0 +1,194 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - name: Try to get docker_swarm_info when docker is not running in swarm mode + docker_swarm_info: + ignore_errors: true + register: output + + - name: assert failure when called when swarm is not in use or not run on mamager node + assert: + that: + - 'output is failed' + - 'output.msg == "Error running docker swarm module: must run on swarm manager node"' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == false' + - 'output.docker_swarm_manager == false' + - 'output.swarm_unlock_key is not defined' + + - name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + register: output + + - name: assert changed when create a new swarm cluster + assert: + that: + - 'output is changed' + - 'output.actions[0] | regex_search("New Swarm cluster created: ")' + - 'output.swarm_facts.JoinTokens.Manager' + - 'output.swarm_facts.JoinTokens.Worker' + + - name: Try to get docker_swarm_info when docker is running in swarm mode and as manager + docker_swarm_info: + register: output + + - name: assert creding docker swarm facts + assert: + that: + - 'output.swarm_facts.JoinTokens.Manager' + - 'output.swarm_facts.JoinTokens.Worker' + - 'output.swarm_facts.ID' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' + + - name: Try to get docker_swarm_info and list of nodes when docker is running in swarm mode and as manager + docker_swarm_info: + nodes: true + register: output + + - name: assert reding swarm facts with list of nodes option + assert: + that: + - 'output.swarm_facts.JoinTokens.Manager' + - 'output.swarm_facts.JoinTokens.Worker' + - 'output.swarm_facts.ID' + - 'output.nodes[0].ID is string' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' + + - name: Get local docker node name + set_fact: + localnodename: "{{ output.nodes[0].Hostname }}" + + + - name: Try to get docker_swarm_info and verbose list of nodes when docker is running in swarm mode and as manager + docker_swarm_info: + nodes: true + verbose_output: true + register: output + + - name: assert reading swarm facts with list of nodes and versbose output options + assert: + that: + - 'output.swarm_facts.JoinTokens.Manager' + - 'output.swarm_facts.JoinTokens.Worker' + - 'output.swarm_facts.ID' + - 'output.nodes[0].ID is string' + - 'output.nodes[0].CreatedAt' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' + + - name: Try to get docker_swarm_info and list of nodes with filters providing existing node name + docker_swarm_info: + nodes: true + nodes_filters: + name: "{{ localnodename }}" + register: output + + - name: assert reading reading swarm facts and using node filter (random node name) + assert: + that: + - 'output.swarm_facts.JoinTokens.Manager' + - 'output.swarm_facts.JoinTokens.Worker' + - 'output.swarm_facts.ID' + - 'output.nodes | length == 1' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' + + - name: Create random name + set_fact: + randomnodename: "{{ 'node-%0x' % ((2**32) | random) }}" + + - name: Try to get docker_swarm_info and list of nodes with filters providing non-existing random node name + docker_swarm_info: + nodes: true + nodes_filters: + name: "{{ randomnodename }}" + register: output + + - name: assert reading reading swarm facts and using node filter (random node name) + assert: + that: + - 'output.swarm_facts.JoinTokens.Manager' + - 'output.swarm_facts.JoinTokens.Worker' + - 'output.swarm_facts.ID' + - 'output.nodes | length == 0' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' + + - name: Try to get docker_swarm_info and swarm_unlock_key on non a unlocked swarm + docker_swarm_info: + unlock_key: true + register: output + ignore_errors: true + + - name: assert reading swarm facts and non existing swarm unlock key + assert: + that: + - 'output.swarm_unlock_key is none' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' + when: docker_py_version is version('2.7.0', '>=') + - assert: + that: + - output is failed + - "('version is ' ~ docker_py_version ~ ' ') in output.msg" + - "'Minimum version required is 2.7.0 ' in output.msg" + when: docker_py_version is version('2.7.0', '<') + + - name: Update swarm cluster to be locked + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + autolock_managers: true + register: autolock_managers_update_output + ignore_errors: true + + - name: Try to get docker_swarm_info and swarm_unlock_key + docker_swarm_info: + unlock_key: true + register: output + ignore_errors: true + + - name: assert reading swarm facts and swarm unlock key + assert: + that: + - 'output.swarm_unlock_key is string' + - 'output.swarm_unlock_key == autolock_managers_update_output.swarm_facts.UnlockKey' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' + when: docker_py_version is version('2.7.0', '>=') + - assert: + that: + - output is failed + - "('version is ' ~ docker_py_version ~ ' ') in output.msg" + - "'Minimum version required is 2.7.0 ' in output.msg" + when: docker_py_version is version('2.7.0', '<') + + always: + - name: Cleanup + docker_swarm: + state: absent + force: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/aliases new file mode 100644 index 000000000..fc581d544 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/3 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/files/env-file-1 b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/files/env-file-1 new file mode 100644 index 000000000..87bc9decd --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/files/env-file-1 @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +TEST3=val3 +TEST4=val4 diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/files/env-file-2 b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/files/env-file-2 new file mode 100644 index 000000000..7f36b44ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/files/env-file-2 @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +TEST3=val5 +TEST5=val5 diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/main.yml new file mode 100644 index 000000000..5a5795b5f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/main.yml @@ -0,0 +1,83 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + + +# Create random name prefix (for containers, networks, ...) +- name: Create random name prefix + set_fact: + name_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + service_names: [] + network_names: [] + config_names: [] + secret_names: [] + volume_names: [] + +- debug: + msg: "Using container name prefix {{ name_prefix }}" + +# Run the tests +- block: + - name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: Make sure all services are removed + docker_swarm_service: + name: "{{ item }}" + state: absent + loop: "{{ service_names }}" + ignore_errors: true + + - name: Make sure all networks are removed + docker_network: + name: "{{ item }}" + state: absent + force: true + loop: "{{ network_names }}" + ignore_errors: true + + - name: Make sure all configs are removed + docker_config: + name: "{{ item }}" + state: absent + force: true + loop: "{{ config_names }}" + ignore_errors: true + + - name: Make sure all volumes are removed + docker_volume: + name: "{{ item }}" + state: absent + loop: "{{ volume_names }}" + ignore_errors: true + + - name: Make sure all secrets are removed + docker_secret: + name: "{{ item }}" + state: absent + force: true + loop: "{{ secret_names }}" + ignore_errors: true + + - name: Make sure swarm is removed + docker_swarm: + state: absent + force: true + ignore_errors: true + when: docker_py_version is version('2.0.2', '>=') and docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_swarm_service tests!" + when: not(docker_py_version is version('2.0.2', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/configs.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/configs.yml new file mode 100644 index 000000000..9f2fa8c3b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/configs.yml @@ -0,0 +1,463 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + service_name: "{{ name_prefix ~ '-configs' }}" + config_name_1: "{{ name_prefix ~ '-configs-1' }}" + config_name_2: "{{ name_prefix ~ '-configs-2' }}" + config_name_3: "{{ name_prefix ~ '-configs-3' }}" + +- name: Registering container name + set_fact: + config_names: "{{ config_names + [config_name_1, config_name_2] }}" + +- docker_config: + name: "{{ config_name_1 }}" + data: "hello" + state: present + register: "config_result_1" + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') + +- docker_config: + name: "{{ config_name_2 }}" + data: "test" + state: present + register: "config_result_2" + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') + +- docker_config: + name: "{{ config_name_3 }}" + data: "config3" + state: present + rolling_versions: true + register: "config_result_3" + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') + +#################################################################### +## configs ######################################################### +#################################################################### + +- name: configs + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + filename: "/tmp/{{ config_name_1 }}.txt" + register: configs_1 + ignore_errors: true + +- name: configs (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_name: "{{ config_name_1 }}" + filename: "/tmp/{{ config_name_1 }}.txt" + register: configs_2 + ignore_errors: true + +- name: configs (add) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + filename: "/tmp/{{ config_name_1 }}.txt" + - config_name: "{{ config_name_2 }}" + filename: "/tmp/{{ config_name_2 }}.txt" + register: configs_3 + ignore_errors: true + +- name: configs (add idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_name: "{{ config_name_1 }}" + filename: "/tmp/{{ config_name_1 }}.txt" + - config_id: "{{ config_result_2.config_id|default('') }}" + config_name: "{{ config_name_2 }}" + filename: "/tmp/{{ config_name_2 }}.txt" + register: configs_4 + ignore_errors: true + +- name: configs (add idempotency no id) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_name: "{{ config_name_1 }}" + filename: "/tmp/{{ config_name_1 }}.txt" + - config_name: "{{ config_name_2 }}" + filename: "/tmp/{{ config_name_2 }}.txt" + register: configs_5 + ignore_errors: true + +- name: configs (add idempotency no id and re-ordered) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_name: "{{ config_name_2 }}" + filename: "/tmp/{{ config_name_2 }}.txt" + - config_name: "{{ config_name_1 }}" + filename: "/tmp/{{ config_name_1 }}.txt" + register: configs_6 + ignore_errors: true + +- name: configs (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: [] + register: configs_7 + ignore_errors: true + +- name: configs (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: [] + register: configs_8 + ignore_errors: true + +- name: rolling configs + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_name: "{{ config_name_3 }}_v1" + filename: "/run/configs/{{ config_name_3 }}.txt" + register: configs_9 + ignore_errors: true + +- name: update rolling config + docker_config: + name: "{{ config_name_3 }}" + data: "newconfig3" + state: "present" + rolling_versions: true + register: configs_10 + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') + ignore_errors: true + +- name: rolling configs service update + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_name: "{{ config_name_3 }}_v2" + filename: "/run/configs/{{ config_name_3 }}.txt" + register: configs_11 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - configs_1 is changed + - configs_2 is not changed + - configs_3 is changed + - configs_4 is not changed + - configs_5 is not changed + - configs_6 is not changed + - configs_7 is changed + - configs_8 is not changed + - configs_9 is changed + - configs_10 is not failed + - configs_11 is changed + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') + +- assert: + that: + - configs_1 is failed + - "'Minimum version required' in configs_1.msg" + when: docker_api_version is version('1.30', '<') or docker_py_version is version('2.6.0', '<') + +#################################################################### +## configs (uid) ################################################### +#################################################################### + +- name: configs (uid int) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + uid: 1000 + register: configs_1 + ignore_errors: true + +- name: configs (uid int idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + uid: 1000 + register: configs_2 + ignore_errors: true + +- name: configs (uid int change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + uid: 1002 + register: configs_3 + ignore_errors: true + +- name: configs (uid str) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + uid: "1001" + register: configs_4 + ignore_errors: true + +- name: configs (uid str idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + uid: "1001" + register: configs_5 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false +- assert: + that: + - configs_1 is changed + - configs_2 is not changed + - configs_3 is changed + - configs_4 is changed + - configs_5 is not changed + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') + +- assert: + that: + - configs_1 is failed + - "'Minimum version required' in configs_1.msg" + when: docker_api_version is version('1.30', '<') or docker_py_version is version('2.6.0', '<') + + +#################################################################### +## configs (gid) ################################################### +#################################################################### + +- name: configs (gid int) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + gid: 1000 + register: configs_1 + ignore_errors: true + +- name: configs (gid int idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + gid: 1000 + register: configs_2 + ignore_errors: true + +- name: configs (gid int change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + gid: 1002 + register: configs_3 + ignore_errors: true + +- name: configs (gid str) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + gid: "1001" + register: configs_4 + ignore_errors: true + +- name: configs (gid str idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + gid: "1001" + register: configs_5 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false +- assert: + that: + - configs_1 is changed + - configs_2 is not changed + - configs_3 is changed + - configs_4 is changed + - configs_5 is not changed + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') + +- assert: + that: + - configs_1 is failed + - "'Minimum version required' in configs_1.msg" + when: docker_api_version is version('1.30', '<') or docker_py_version is version('2.6.0', '<') + +#################################################################### +## configs (mode) ################################################## +#################################################################### + +- name: configs (mode) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + mode: 0600 + register: configs_1 + ignore_errors: true + +- name: configs (mode idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + mode: 0600 + register: configs_2 + ignore_errors: true + +- name: configs (mode change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_id: "{{ config_result_1.config_id|default('') }}" + config_name: "{{ config_name_1 }}" + mode: 0777 + register: configs_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false +- assert: + that: + - configs_1 is changed + - configs_2 is not changed + - configs_3 is changed + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') + +- assert: + that: + - configs_1 is failed + - "'Minimum version required' in configs_1.msg" + when: docker_api_version is version('1.30', '<') or docker_py_version is version('2.6.0', '<') + +#################################################################### +#################################################################### +#################################################################### + +- name: Delete configs + docker_config: + name: "{{ config_name }}" + state: absent + force: true + loop: + - "{{ config_name_1 }}" + - "{{ config_name_2 }}" + - "{{ config_name_3 }}" + loop_control: + loop_var: config_name + ignore_errors: true + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/logging.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/logging.yml new file mode 100644 index 000000000..22947fbd9 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/logging.yml @@ -0,0 +1,138 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-logging' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + +#################################################################### +## logging.driver ################################################## +#################################################################### + +- name: logging.driver + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + logging: + driver: json-file + register: logging_driver_1 + +- name: logging.driver (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + logging: + driver: json-file + register: logging_driver_2 + +- name: logging.driver (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + logging: + driver: syslog + register: logging_driver_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - logging_driver_1 is changed + - logging_driver_2 is not changed + - logging_driver_3 is changed + +#################################################################### +## logging.options ################################################# +#################################################################### + +- name: logging_options + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + logging: + driver: json-file + options: + labels: production_status + env: os,customer + register: logging_options_1 + +- name: logging_options (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + logging: + driver: json-file + options: + env: os,customer + labels: production_status + register: logging_options_2 + +- name: logging_options (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + logging: + driver: json-file + options: + env: os,customer + labels: production_status + max-file: "1" + register: logging_options_3 + +- name: logging_options (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + logging: + driver: json-file + options: {} + register: logging_options_4 + +- name: logging_options (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + logging: + driver: json-file + options: {} + register: logging_options_5 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - logging_options_1 is changed + - logging_options_2 is not changed + - logging_options_3 is changed + - logging_options_4 is changed + - logging_options_5 is not changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/misc.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/misc.yml new file mode 100644 index 000000000..a1e185e17 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/misc.yml @@ -0,0 +1,117 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- block: + - name: Create a swarm service without name + register: output + docker_swarm_service: + state: present + ignore_errors: true + + - name: assert failure when name not set + assert: + that: + - output is failed + - 'output.msg == "missing required arguments: name"' + + - name: Remove an non-existing service + register: output + docker_swarm_service: + state: absent + name: non_existing_service + + - name: assert output not changed when deleting non-existing service + assert: + that: + - output is not changed + + - name: create sample service + register: output + docker_swarm_service: + name: test_service + endpoint_mode: dnsrr + image: "{{ docker_test_image_busybox }}" + resolve_image: false + args: + - sleep + - "3600" + + - name: assert sample service is created + assert: + that: + - output is changed + + - name: change service args + register: output + docker_swarm_service: + name: test_service + image: "{{ docker_test_image_busybox }}" + resolve_image: false + args: + - sleep + - "1800" + + - name: assert service args are correct + assert: + that: + - output.swarm_service.args == ['sleep', '1800'] + + - name: set service mode to global + register: output + docker_swarm_service: + name: test_service + image: "{{ docker_test_image_busybox }}" + resolve_image: false + endpoint_mode: vip + mode: global + args: + - sleep + - "1800" + + - name: assert service mode changed caused service rebuild + assert: + that: + - output.rebuilt + + - name: add published ports to service + register: output + docker_swarm_service: + name: test_service + image: "{{ docker_test_image_busybox }}" + resolve_image: false + mode: global + args: + - sleep + - "1800" + endpoint_mode: vip + publish: + - protocol: tcp + published_port: 60001 + target_port: 60001 + - protocol: udp + published_port: 60001 + target_port: 60001 + + - name: fake image key as it is not predictable + set_fact: + ansible_docker_service_output: "{{ output.swarm_service|combine({'image': docker_test_image_busybox}) }}" + + - name: assert service matches expectations + assert: + that: + - ansible_docker_service_output == service_expected_output + + - name: delete sample service + register: output + docker_swarm_service: + name: test_service + state: absent + + - name: assert service deletion returns changed + assert: + that: + - output is success + - output is changed + when: docker_api_version is version('1.25', '>=') and docker_py_version is version('3.0.0', '>=') diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/mounts.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/mounts.yml new file mode 100644 index 000000000..7605d9fc8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/mounts.yml @@ -0,0 +1,606 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-mounts' }}" + volume_name_1: "{{ name_prefix ~ '-volume-1' }}" + volume_name_2: "{{ name_prefix ~ '-volume-2' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + volume_names: "{{ volume_names + [volume_name_1, volume_name_2] }}" + +- docker_volume: + name: "{{ volume_name }}" + state: present + loop: + - "{{ volume_name_1 }}" + - "{{ volume_name_2 }}" + loop_control: + loop_var: volume_name + +#################################################################### +## mounts ########################################################## +#################################################################### + +- name: mounts + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + register: mounts_1 + +- name: mounts (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + register: mounts_2 + +- name: mounts (add) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + - source: "/tmp/" + target: "/tmp/{{ volume_name_2 }}" + type: "bind" + register: mounts_3 + +- name: mounts (order idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "/tmp/" + target: "/tmp/{{ volume_name_2 }}" + type: "bind" + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + register: mounts_4 + +- name: mounts (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: [] + register: mounts_5 + +- name: mounts (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: [] + register: mounts_6 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_1 is changed + - mounts_2 is not changed + - mounts_3 is changed + - mounts_4 is not changed + - mounts_5 is changed + - mounts_6 is not changed + +#################################################################### +## mounts.readonly ################################################# +#################################################################### + +- name: mounts.readonly + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + readonly: true + register: mounts_readonly_1 + + +- name: mounts.readonly (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + readonly: true + register: mounts_readonly_2 + +- name: mounts.readonly (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + readonly: false + register: mounts_readonly_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_readonly_1 is changed + - mounts_readonly_2 is not changed + - mounts_readonly_3 is changed + +#################################################################### +## mounts.propagation ############################################## +#################################################################### + +- name: mounts.propagation + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "/tmp" + target: "/tmp/{{ volume_name_1 }}" + type: "bind" + propagation: "slave" + register: mounts_propagation_1 + + +- name: mounts.propagation (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "/tmp" + target: "/tmp/{{ volume_name_1 }}" + type: "bind" + propagation: "slave" + register: mounts_propagation_2 + +- name: mounts.propagation (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "/tmp" + target: "/tmp/{{ volume_name_1 }}" + type: "bind" + propagation: "rprivate" + register: mounts_propagation_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_propagation_1 is changed + - mounts_propagation_2 is not changed + - mounts_propagation_3 is changed + +#################################################################### +## mounts.labels ################################################## +#################################################################### + +- name: mounts.labels + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + labels: + mylabel: hello-world + my-other-label: hello-mars + register: mounts_labels_1 + + +- name: mounts.labels (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + labels: + mylabel: hello-world + my-other-label: hello-mars + register: mounts_labels_2 + +- name: mounts.labels (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + labels: + mylabel: hello-world + register: mounts_labels_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_labels_1 is changed + - mounts_labels_2 is not changed + - mounts_labels_3 is changed + +#################################################################### +## mounts.no_copy ################################################## +#################################################################### + +- name: mounts.no_copy + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + no_copy: true + register: mounts_no_copy_1 + + +- name: mounts.no_copy (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + no_copy: true + register: mounts_no_copy_2 + +- name: mounts.no_copy (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + no_copy: false + register: mounts_no_copy_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_no_copy_1 is changed + - mounts_no_copy_2 is not changed + - mounts_no_copy_3 is changed + +#################################################################### +## mounts.driver_config ############################################ +#################################################################### + +- name: mounts.driver_config + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + driver_config: + name: "nfs" + options: + addr: "127.0.0.1" + register: mounts_driver_config_1 + +- name: mounts.driver_config + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + driver_config: + name: "nfs" + options: + addr: "127.0.0.1" + register: mounts_driver_config_2 + +- name: mounts.driver_config + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "volume" + driver_config: + name: "local" + register: mounts_driver_config_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_driver_config_1 is changed + - mounts_driver_config_2 is not changed + - mounts_driver_config_3 is changed + +#################################################################### +## mounts.tmpfs_size ############################################### +#################################################################### + +- name: mounts.tmpfs_size + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + tmpfs_size: "50M" + register: mounts_tmpfs_size_1 + ignore_errors: true + +- name: mounts.tmpfs_size (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + tmpfs_size: "50M" + register: mounts_tmpfs_size_2 + ignore_errors: true + +- name: mounts.tmpfs_size (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + tmpfs_size: "25M" + register: mounts_tmpfs_size_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_tmpfs_size_1 is changed + - mounts_tmpfs_size_2 is not changed + - mounts_tmpfs_size_3 is changed + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - mounts_tmpfs_size_1 is failed + - "'Minimum version required' in mounts_tmpfs_size_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## mounts.tmpfs_mode ############################################### +#################################################################### + +- name: mounts.tmpfs_mode + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + tmpfs_mode: 0444 + register: mounts_tmpfs_mode_1 + ignore_errors: true + +- name: mounts.tmpfs_mode (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + tmpfs_mode: 0444 + register: mounts_tmpfs_mode_2 + ignore_errors: true + +- name: mounts.tmpfs_mode (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "{{ volume_name_1 }}" + target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + tmpfs_mode: 0777 + register: mounts_tmpfs_mode_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_tmpfs_mode_1 is changed + - mounts_tmpfs_mode_2 is not changed + - mounts_tmpfs_mode_3 is changed + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - mounts_tmpfs_size_1 is failed + - "'Minimum version required' in mounts_tmpfs_size_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## mounts.source ################################################### +#################################################################### + +- name: mounts.source (empty for tmpfs) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "" + target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + register: mounts_tmpfs_source_1 + ignore_errors: true + +- name: mounts.source (empty for tmpfs idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - source: "" + target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + register: mounts_tmpfs_source_2 + ignore_errors: true + +- name: mounts.source (not specified for tmpfs idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mounts: + - target: "/tmp/{{ volume_name_1 }}" + type: "tmpfs" + register: mounts_tmpfs_source_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mounts_tmpfs_source_1 is changed + - mounts_tmpfs_source_2 is not changed + - mounts_tmpfs_source_3 is not changed + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - mounts_tmpfs_source_1 is failed + - "'Minimum version required' in mounts_tmpfs_source_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +#################################################################### +#################################################################### + +- name: Delete volumes + docker_volume: + name: "{{ volume_name }}" + state: absent + loop: + - "{{ volume_name_1 }}" + - "{{ volume_name_2 }}" + loop_control: + loop_var: volume_name + ignore_errors: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/networks.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/networks.yml new file mode 100644 index 000000000..f57824f92 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/networks.yml @@ -0,0 +1,453 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-networks' }}" + network_name_1: "{{ name_prefix ~ '-network-1' }}" + network_name_2: "{{ name_prefix ~ '-network-2' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + network_names: "{{ network_names + [network_name_1, network_name_2] }}" + +- docker_network: + name: "{{ network_name }}" + driver: "overlay" + state: present + loop: + - "{{ network_name_1 }}" + - "{{ network_name_2 }}" + loop_control: + loop_var: network_name + +##################################################################### +## networks ######################################################### +##################################################################### + +- name: networks + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - "{{ network_name_1 }}" + register: networks_1 + +- name: networks (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - "{{ network_name_1 }}" + register: networks_2 + +- name: networks (dict idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + register: networks_3 + +- name: networks (change more) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - "{{ network_name_1 }}" + - "{{ network_name_2 }}" + register: networks_4 + +- name: networks (change more idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - "{{ network_name_1 }}" + - "{{ network_name_2 }}" + register: networks_5 + +- name: networks (change more dict idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + - name: "{{ network_name_2 }}" + register: networks_6 + +- name: networks (change more mixed idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + - "{{ network_name_2 }}" + register: networks_7 + +- name: networks (order idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - "{{ network_name_2 }}" + - name: "{{ network_name_1 }}" + register: networks_8 + +- name: networks (change less) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - "{{ network_name_2 }}" + register: networks_9 + +- name: networks (change less idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - "{{ network_name_2 }}" + register: networks_10 + +- name: networks (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: [] + register: networks_11 + +- name: networks (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: [] + register: networks_12 + +- name: networks (unknown network) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - "idonotexist" + register: networks_13 + ignore_errors: true + +- name: networks (missing dict key name) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - foo: "bar" + register: networks_14 + ignore_errors: true + +- name: networks (invalid list type) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - [1, 2, 3] + register: networks_15 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - networks_1 is changed + - networks_2 is not changed + - networks_3 is not changed + - networks_4 is changed + - networks_5 is not changed + - networks_6 is not changed + - networks_7 is not changed + - networks_8 is not changed + - networks_9 is changed + - networks_10 is not changed + - networks_11 is changed + - networks_12 is not changed + - networks_13 is failed + - '"Could not find a network named: ''idonotexist''" in networks_13.msg' + - networks_14 is failed + - "'\"name\" is required when networks are passed as dictionaries.' in networks_14.msg" + - networks_15 is failed + - "'Only a list of strings or dictionaries are allowed to be passed as networks' in networks_15.msg" + +- assert: + that: + - networks_4.rebuilt == false + - networks_7.rebuilt == false + when: docker_api_version is version('1.29', '>=') and docker_py_version is version('2.7.0', '>=') + +- assert: + that: + - networks_4.rebuilt == true + - networks_7.rebuilt == true + when: docker_api_version is version('1.29', '<') or docker_py_version is version('2.7.0', '<') + +#################################################################### +## networks.aliases ################################################ +#################################################################### + +- name: networks.aliases + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + aliases: + - "alias1" + - "alias2" + register: networks_aliases_1 + +- name: networks.aliases (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + aliases: + - "alias1" + - "alias2" + register: networks_aliases_2 + +- name: networks.aliases (order idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + aliases: + - "alias2" + - "alias1" + register: networks_aliases_3 + +- name: networks.aliases (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + aliases: + - "alias1" + register: networks_aliases_4 + +- name: networks.aliases (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + aliases: [] + register: networks_aliases_5 + +- name: networks.aliases (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + aliases: [] + register: networks_aliases_6 + +- name: networks.aliases (invalid type) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + aliases: + - [1, 2, 3] + register: networks_aliases_7 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - networks_aliases_1 is changed + - networks_aliases_2 is not changed + - networks_aliases_3 is not changed + - networks_aliases_4 is changed + - networks_aliases_5 is changed + - networks_aliases_6 is not changed + - networks_aliases_7 is failed + - "'Only strings are allowed as network aliases' in networks_aliases_7.msg" + +#################################################################### +## networks.options ################################################ +#################################################################### + +- name: networks.options + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + options: + foo: bar + test: hello + register: networks_options_1 + +- name: networks.options (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + options: + foo: bar + test: hello + register: networks_options_2 + +- name: networks.options (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + options: + foo: bar + test: hej + register: networks_options_3 + +- name: networks.options (change less) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + options: + foo: bar + register: networks_options_4 + +- name: networks.options (invalid type) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + options: [1, 2, 3] + register: networks_options_5 + ignore_errors: true + +- name: networks.options (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + options: {} + register: networks_options_6 + +- name: networks.options (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + networks: + - name: "{{ network_name_1 }}" + options: {} + register: networks_options_7 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - networks_options_1 is changed + - networks_options_2 is not changed + - networks_options_3 is changed + - networks_options_4 is changed + - networks_options_5 is failed + - "'Only dict is allowed as network options' in networks_options_5.msg" + - networks_options_6 is changed + - networks_options_7 is not changed + +#################################################################### +#################################################################### +#################################################################### + +- name: Delete networks + docker_network: + name: "{{ network_name }}" + state: absent + force: true + loop: + - "{{ network_name_1 }}" + - "{{ network_name_2 }}" + loop_control: + loop_var: network_name + ignore_errors: true diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/options.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/options.yml new file mode 100644 index 000000000..7b2066984 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/options.yml @@ -0,0 +1,2005 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-options' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + +#################################################################### +## args ############################################################ +#################################################################### + +- name: args + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + args: + - sleep + - "3600" + register: args_1 + +- name: args (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + args: + - sleep + - "3600" + register: args_2 + +- name: args (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + args: + - sleep + - "3400" + register: args_3 + +- name: args (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + args: [] + register: args_4 + +- name: args (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + args: [] + register: args_5 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - args_1 is changed + - args_2 is not changed + - args_3 is changed + - args_4 is changed + - args_5 is not changed + +#################################################################### +## command ######################################################### +#################################################################### + +- name: command + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + register: command_1 + +- name: command (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + register: command_2 + +- name: command (less parameters) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -c "sleep 10m"' + register: command_3 + +- name: command (as list) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: + - "/bin/sh" + - "-c" + - "sleep 10m" + register: command_4 + +- name: command (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: [] + register: command_5 + +- name: command (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: [] + register: command_6 + +- name: command (string failure) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: true + register: command_7 + ignore_errors: true + +- name: command (list failure) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: + - "/bin/sh" + - true + register: command_8 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - command_1 is changed + - command_2 is not changed + - command_3 is changed + - command_4 is not changed + - command_5 is changed + - command_6 is not changed + - command_7 is failed + - command_8 is failed + +#################################################################### +## container_labels ################################################ +#################################################################### + +- name: container_labels + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + container_labels: + test_1: "1" + test_2: "2" + register: container_labels_1 + +- name: container_labels (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + container_labels: + test_1: "1" + test_2: "2" + register: container_labels_2 + +- name: container_labels (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + container_labels: + test_1: "1" + test_2: "3" + register: container_labels_3 + +- name: container_labels (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + container_labels: {} + register: container_labels_4 + +- name: container_labels (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + container_labels: {} + register: container_labels_5 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - container_labels_1 is changed + - container_labels_2 is not changed + - container_labels_3 is changed + - container_labels_4 is changed + - container_labels_5 is not changed + +#################################################################### +## dns ############################################################# +#################################################################### + +- name: dns + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns: + - 1.1.1.1 + - 8.8.8.8 + register: dns_1 + ignore_errors: true + +- name: dns (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns: + - 1.1.1.1 + - 8.8.8.8 + register: dns_2 + ignore_errors: true + +- name: dns_servers (changed order) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns: + - 8.8.8.8 + - 1.1.1.1 + register: dns_3 + ignore_errors: true + +- name: dns_servers (changed elements) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns: + - 8.8.8.8 + - 9.9.9.9 + register: dns_4 + ignore_errors: true + +- name: dns_servers (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns: [] + register: dns_5 + ignore_errors: true + +- name: dns_servers (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns: [] + register: dns_6 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - dns_1 is changed + - dns_2 is not changed + - dns_3 is changed + - dns_4 is changed + - dns_5 is changed + - dns_6 is not changed + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - dns_1 is failed + - "'Minimum version required' in dns_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## dns_options ##################################################### +#################################################################### + +- name: dns_options + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_options: + - "timeout:10" + - rotate + register: dns_options_1 + ignore_errors: true + +- name: dns_options (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_options: + - "timeout:10" + - rotate + register: dns_options_2 + ignore_errors: true + +- name: dns_options (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_options: + - "timeout:10" + - no-check-names + register: dns_options_3 + ignore_errors: true + +- name: dns_options (order idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_options: + - no-check-names + - "timeout:10" + register: dns_options_4 + ignore_errors: true + +- name: dns_options (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_options: [] + register: dns_options_5 + ignore_errors: true + +- name: dns_options (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_options: [] + register: dns_options_6 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - dns_options_1 is changed + - dns_options_2 is not changed + - dns_options_3 is changed + - dns_options_4 is not changed + - dns_options_5 is changed + - dns_options_6 is not changed + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - dns_options_1 is failed + - "'Minimum version required' in dns_options_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## dns_search ###################################################### +#################################################################### + +- name: dns_search + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_search: + - example.com + - example.org + register: dns_search_1 + ignore_errors: true + +- name: dns_search (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_search: + - example.com + - example.org + register: dns_search_2 + ignore_errors: true + +- name: dns_search (different order) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_search: + - example.org + - example.com + register: dns_search_3 + ignore_errors: true + +- name: dns_search (changed elements) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_search: + - ansible.com + - example.com + register: dns_search_4 + ignore_errors: true + +- name: dns_search (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_search: [] + register: dns_search_5 + ignore_errors: true + +- name: dns_search (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + dns_search: [] + register: dns_search_6 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - dns_search_1 is changed + - dns_search_2 is not changed + - dns_search_3 is changed + - dns_search_4 is changed + - dns_search_5 is changed + - dns_search_6 is not changed + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - dns_search_1 is failed + - "'Minimum version required' in dns_search_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## endpoint_mode ################################################### +#################################################################### + +- name: endpoint_mode + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + endpoint_mode: "dnsrr" + register: endpoint_mode_1 + ignore_errors: true + +- name: endpoint_mode (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + endpoint_mode: "dnsrr" + register: endpoint_mode_2 + ignore_errors: true + +- name: endpoint_mode (changes) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + endpoint_mode: "vip" + register: endpoint_mode_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - endpoint_mode_1 is changed + - endpoint_mode_2 is not changed + - endpoint_mode_3 is changed + when: docker_py_version is version('3.0.0', '>=') +- assert: + that: + - endpoint_mode_1 is failed + - "'Minimum version required' in endpoint_mode_1.msg" + when: docker_py_version is version('3.0.0', '<') + +#################################################################### +## env ############################################################# +#################################################################### + +- name: env + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + env: + - "TEST1=val1" + - "TEST2=val2" + register: env_1 + +- name: env (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + env: + TEST1: val1 + TEST2: val2 + register: env_2 + +- name: env (changes) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + env: + - "TEST1=val1" + - "TEST2=val3" + register: env_3 + +- name: env (order idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + env: + - "TEST2=val3" + - "TEST1=val1" + register: env_4 + +- name: env (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + env: [] + register: env_5 + +- name: env (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + env: [] + register: env_6 + +- name: env (fail unwrapped values) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env: + TEST1: true + register: env_7 + ignore_errors: true + +- name: env (fail invalid formatted string) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env: + - "TEST1=val3" + - "TEST2" + register: env_8 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - env_1 is changed + - env_2 is not changed + - env_3 is changed + - env_4 is not changed + - env_5 is changed + - env_6 is not changed + - env_7 is failed + - env_8 is failed + +#################################################################### +## env_files ####################################################### +#################################################################### + +- name: Copy env-files + copy: + src: "{{ item }}" + dest: "{{ remote_tmp_dir }}/{{ item }}" + loop: + - env-file-1 + - env-file-2 + +- name: env_files + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env_files: + - "{{ remote_tmp_dir }}/env-file-1" + register: env_file_1 + +- name: env_files (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env_files: + - "{{ remote_tmp_dir }}/env-file-1" + register: env_file_2 + +- name: env_files (more items) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env_files: + - "{{ remote_tmp_dir }}/env-file-1" + - "{{ remote_tmp_dir }}/env-file-2" + register: env_file_3 + +- name: env_files (order) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env_files: + - "{{ remote_tmp_dir }}/env-file-2" + - "{{ remote_tmp_dir }}/env-file-1" + register: env_file_4 + +- name: env_files (multiple idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env_files: + - "{{ remote_tmp_dir }}/env-file-2" + - "{{ remote_tmp_dir }}/env-file-1" + register: env_file_5 + +- name: env_files (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env_files: [] + register: env_file_6 + +- name: env_files (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + env_files: [] + register: env_file_7 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - env_file_1 is changed + - env_file_2 is not changed + - env_file_3 is changed + - env_file_4 is changed + - env_file_5 is not changed + - env_file_6 is changed + - env_file_7 is not changed + +################################################################### +## force_update ################################################### +################################################################### + +- name: force_update + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + args: + - sleep + - "3600" + force_update: true + register: force_update_1 + ignore_errors: true + +- name: force_update (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + args: + - sleep + - "3600" + force_update: true + register: force_update_2 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - force_update_1 is changed + - force_update_2 is changed + when: docker_py_version is version('2.1.0', '>=') +- assert: + that: + - force_update_1 is failed + - "'Minimum version required' in force_update_1.msg" + when: docker_py_version is version('2.1.0', '<') + +#################################################################### +## groups ########################################################## +#################################################################### + +- name: groups + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + groups: + - "1234" + - "5678" + register: groups_1 + ignore_errors: true + +- name: groups (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + groups: + - "1234" + - "5678" + register: groups_2 + ignore_errors: true + +- name: groups (order idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + groups: + - "5678" + - "1234" + register: groups_3 + ignore_errors: true + +- name: groups (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + groups: + - "1234" + register: groups_4 + ignore_errors: true + +- name: groups (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + groups: [] + register: groups_5 + ignore_errors: true + +- name: groups (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + groups: [] + register: groups_6 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - groups_1 is changed + - groups_2 is not changed + - groups_3 is not changed + - groups_4 is changed + - groups_5 is changed + - groups_6 is not changed + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - groups_1 is failed + - "'Minimum version required' in groups_1.msg" + when: docker_py_version is version('2.6.0', '<') + +#################################################################### +## healthcheck ##################################################### +#################################################################### + +- name: healthcheck + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: + test: + - CMD + - sleep + - "1" + timeout: 2s + interval: 0h0m2s3ms4us + retries: 2 + start_period: 20s + register: healthcheck_1 + ignore_errors: true + +- name: healthcheck (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: + test: + - CMD + - sleep + - 1 + timeout: 2s + interval: 0h0m2s3ms4us + retries: 2 + start_period: 20s + register: healthcheck_2 + ignore_errors: true + +- name: healthcheck (changed) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: + test: + - CMD + - sleep + - "1" + timeout: 3s + interval: 0h1m2s3ms4us + retries: 3 + register: healthcheck_3 + ignore_errors: true + +- name: healthcheck (disabled) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: + test: + - NONE + register: healthcheck_4 + ignore_errors: true + +- name: healthcheck (disabled, idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: + test: + - NONE + register: healthcheck_5 + ignore_errors: true + +- name: healthcheck (string in healthcheck test, changed) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: + test: "sleep 1" + register: healthcheck_6 + ignore_errors: true + +- name: healthcheck (string in healthcheck test, idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: + test: "sleep 1" + register: healthcheck_7 + ignore_errors: true + +- name: healthcheck (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: {} + register: healthcheck_8 + ignore_errors: true + +- name: healthcheck (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + healthcheck: {} + register: healthcheck_9 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - healthcheck_1 is changed + - healthcheck_2 is not changed + - healthcheck_3 is changed + - healthcheck_4 is changed + - healthcheck_5 is not changed + - healthcheck_6 is changed + - healthcheck_7 is not changed + - healthcheck_8 is changed + - healthcheck_9 is not changed + when: docker_api_version is version('1.29', '>=') and docker_py_version is version('2.6.0', '>=') +- assert: + that: + - healthcheck_1 is failed + - "'Minimum version required' in healthcheck_1.msg" + when: docker_api_version is version('1.29', '<') or docker_py_version is version('2.6.0', '<') + +################################################################### +## hostname ####################################################### +################################################################### + +- name: hostname + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + hostname: me.example.com + register: hostname_1 + ignore_errors: true + +- name: hostname (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + hostname: me.example.com + register: hostname_2 + ignore_errors: true + +- name: hostname (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + hostname: me.example.org + register: hostname_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - hostname_1 is changed + - hostname_2 is not changed + - hostname_3 is changed + when: docker_py_version is version('2.2.0', '>=') +- assert: + that: + - hostname_1 is failed + - "'Minimum version required' in hostname_1.msg" + when: docker_py_version is version('2.2.0', '<') + +################################################################### +## hosts ########################################################## +################################################################### + +- name: hosts + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + hosts: + example.com: 1.2.3.4 + example.org: 4.3.2.1 + register: hosts_1 + ignore_errors: true + +- name: hosts (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + hosts: + example.com: 1.2.3.4 + example.org: 4.3.2.1 + register: hosts_2 + ignore_errors: true + +- name: hosts (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + hosts: + example.com: 1.2.3.4 + register: hosts_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - hosts_1 is changed + - hosts_2 is not changed + - hosts_3 is changed + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - hosts_1 is failed + - "'Minimum version required' in hosts_1.msg" + when: docker_py_version is version('2.6.0', '<') + + +################################################################### +## image ########################################################## +################################################################### + +- name: image + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + register: image_1 + +- name: image (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + register: image_2 + +- name: image (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine_different }}" + register: image_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - image_1 is changed + - image_2 is not changed + - image_3 is changed + +#################################################################### +## labels ########################################################## +#################################################################### + +- name: labels + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + labels: + test_1: "1" + test_2: "2" + register: labels_1 + +- name: labels (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + labels: + test_1: "1" + test_2: "2" + register: labels_2 + +- name: labels (changes) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + labels: + test_1: "1" + test_2: "2" + test_3: "3" + register: labels_3 + +- name: labels (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + labels: {} + register: labels_4 + +- name: labels (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + labels: {} + register: labels_5 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - labels_1 is changed + - labels_2 is not changed + - labels_3 is changed + - labels_4 is changed + - labels_5 is not changed + +################################################################### +## mode ########################################################### +################################################################### + +- name: mode + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mode: "replicated" + replicas: 1 + register: mode_1 + +- name: mode (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mode: "replicated" + replicas: 1 + register: mode_2 + +- name: mode (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + mode: "global" + replicas: 1 + register: mode_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - mode_1 is changed + - mode_2 is not changed + - mode_3 is changed + +#################################################################### +## stop_grace_period ############################################### +#################################################################### + +- name: stop_grace_period + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + stop_grace_period: 60s + register: stop_grace_period_1 + +- name: stop_grace_period (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + stop_grace_period: 60s + register: stop_grace_period_2 + +- name: stop_grace_period (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + stop_grace_period: 1m30s + register: stop_grace_period_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - stop_grace_period_1 is changed + - stop_grace_period_2 is not changed + - stop_grace_period_3 is changed + +#################################################################### +## stop_signal ##################################################### +#################################################################### + +- name: stop_signal + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + stop_signal: "30" + register: stop_signal_1 + ignore_errors: true + +- name: stop_signal (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + stop_signal: "30" + register: stop_signal_2 + ignore_errors: true + +- name: stop_signal (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + stop_signal: "9" + register: stop_signal_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - stop_signal_1 is changed + - stop_signal_2 is not changed + - stop_signal_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('2.6.0', '>=') +- assert: + that: + - stop_signal_1 is failed + - "'Minimum version required' in stop_signal_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('2.6.0', '<') + +#################################################################### +## publish ######################################################### +#################################################################### + +- name: publish + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: + - protocol: tcp + published_port: 60001 + target_port: 60001 + - protocol: udp + published_port: 60002 + target_port: 60002 + register: publish_1 + ignore_errors: true + +- name: publish (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: + - protocol: udp + published_port: 60002 + target_port: 60002 + - published_port: 60001 + target_port: 60001 + register: publish_2 + ignore_errors: true + +- name: publish (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: + - protocol: tcp + published_port: 60002 + target_port: 60003 + - protocol: udp + published_port: 60001 + target_port: 60001 + register: publish_3 + ignore_errors: true + +- name: publish (mode) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: + - protocol: tcp + published_port: 60002 + target_port: 60003 + mode: host + - protocol: udp + published_port: 60001 + target_port: 60001 + mode: host + register: publish_4 + ignore_errors: true + +- name: publish (mode idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: + - protocol: udp + published_port: 60001 + target_port: 60001 + mode: host + - protocol: tcp + published_port: 60002 + target_port: 60003 + mode: host + register: publish_5 + ignore_errors: true + +- name: publish (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: [] + register: publish_6 + ignore_errors: true + +- name: publish (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: [] + register: publish_7 + ignore_errors: true + +- name: publish (publishes the same port with both protocols) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: + - protocol: udp + published_port: 60001 + target_port: 60001 + mode: host + - protocol: tcp + published_port: 60001 + target_port: 60001 + mode: host + register: publish_8 + ignore_errors: true +- name: gather service info + docker_swarm_service_info: + name: "{{ service_name }}" + register: publish_8_info + +- name: publish (without published_port) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: + - protocol: udp + target_port: 60001 + mode: host + register: publish_9 + ignore_errors: true +- name: gather service info + docker_swarm_service_info: + name: "{{ service_name }}" + register: publish_9_info + +- name: publish (without published_port, idempotence) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + publish: + - protocol: udp + target_port: 60001 + mode: host + register: publish_10 + ignore_errors: true +- name: gather service info + docker_swarm_service_info: + name: "{{ service_name }}" + register: publish_10_info + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - publish_1 is changed + - publish_2 is not changed + - publish_3 is changed + - publish_4 is changed + - publish_5 is not changed + - publish_6 is changed + - publish_7 is not changed + - publish_8 is changed + - (publish_8_info.service.Endpoint.Ports | length) == 2 + - publish_9 is changed + - publish_10 is not changed + when: docker_py_version is version('3.0.0', '>=') +- assert: + that: + - publish_1 is failed + - "'Minimum version required' in publish_1.msg" + when: docker_py_version is version('3.0.0', '<') + +################################################################### +## read_only ###################################################### +################################################################### + +- name: read_only + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + read_only: true + register: read_only_1 + ignore_errors: true + +- name: read_only (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + read_only: true + register: read_only_2 + ignore_errors: true + +- name: read_only (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + read_only: false + register: read_only_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - read_only_1 is changed + - read_only_2 is not changed + - read_only_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('2.6.0', '>=') +- assert: + that: + - read_only_1 is failed + - "'Minimum version required' in read_only_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('2.6.0', '<') + +################################################################### +## replicas ####################################################### +################################################################### + +- name: replicas + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + replicas: 2 + register: replicas_1 + +- name: replicas (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + replicas: 2 + register: replicas_2 + +- name: replicas (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + replicas: 3 + register: replicas_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - replicas_1 is changed + - replicas_2 is not changed + - replicas_3 is changed + +################################################################### +# resolve_image ################################################### +################################################################### + +- name: resolve_image (false) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -v -c "sleep 10m"' + resolve_image: false + register: resolve_image_1 + +- name: resolve_image (false idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -v -c "sleep 10m"' + resolve_image: false + register: resolve_image_2 + +- name: resolve_image (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + command: '/bin/sh -v -c "sleep 10m"' + resolve_image: true + register: resolve_image_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - resolve_image_1 is changed + - resolve_image_2 is not changed + - resolve_image_3 is changed + when: docker_api_version is version('1.30', '>=') and docker_py_version is version('3.2.0', '>=') +- assert: + that: + - resolve_image_1 is changed + - resolve_image_2 is not changed + - resolve_image_3 is failed + - "('version is ' ~ docker_py_version ~ ' ') in resolve_image_3.msg" + - "'Minimum version required is 3.2.0 ' in resolve_image_3.msg" + when: docker_api_version is version('1.30', '<') or docker_py_version is version('3.2.0', '<') + +################################################################### +# tty ############################################################# +################################################################### + +- name: tty + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + tty: true + register: tty_1 + ignore_errors: true + +- name: tty (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + tty: true + register: tty_2 + ignore_errors: true + +- name: tty (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + tty: false + register: tty_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - tty_1 is changed + - tty_2 is not changed + - tty_3 is changed + when: docker_py_version is version('2.4.0', '>=') +- assert: + that: + - tty_1 is failed + - "'Minimum version required' in tty_1.msg" + when: docker_py_version is version('2.4.0', '<') + +################################################################### +## user ########################################################### +################################################################### + +- name: user + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + user: "operator" + register: user_1 + +- name: user (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + user: "operator" + register: user_2 + +- name: user (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + user: "root" + register: user_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - user_1 is changed + - user_2 is not changed + - user_3 is changed + +#################################################################### +## working_dir ##################################################### +#################################################################### + +- name: working_dir + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + working_dir: /tmp + register: working_dir_1 + +- name: working_dir (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + working_dir: /tmp + register: working_dir_2 + +- name: working_dir (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + working_dir: / + register: working_dir_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - working_dir_1 is changed + - working_dir_2 is not changed + - working_dir_3 is changed + +#################################################################### +## init ############################################################ +#################################################################### + +- name: init + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + init: true + register: init_1 + +- name: init (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + init: true + register: init_2 + +- name: init (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + init: false + register: init_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - init_1 is changed + - init_2 is not changed + - init_3 is changed + when: docker_api_version is version('1.37', '>=') + +- assert: + that: + - init_1 is failed + - "('version is ' ~ docker_api_version ~'. Minimum version required is 1.37') in hosts_1.msg" + when: docker_api_version is version('1.37', '<') + +#################################################################### +## cap_drop, capabilities ########################################## +#################################################################### + +- name: capabilities, cap_drop + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + init: true + cap_add: + - sys_time + cap_drop: + - all + register: capabilities_1 + ignore_errors: true + +- name: capabilities, cap_drop (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + init: true + cap_add: + - sys_time + cap_drop: + - all + register: capabilities_2 + ignore_errors: true + diff: true + +- name: capabilities, cap_drop (less) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + init: true + cap_add: [] + cap_drop: + - all + register: capabilities_3 + ignore_errors: true + diff: true + +- name: capabilities, cap_drop (changed) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + init: true + cap_add: + - setgid + cap_drop: + - all + register: capabilities_4 + ignore_errors: true + diff: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - capabilities_1 is changed + - capabilities_2 is not changed + - capabilities_3 is changed + - capabilities_4 is changed + when: docker_api_version is version('1.41', '>=') and docker_py_version is version('5.0.3', '>=') + +- assert: + that: + - capabilities_1 is failed + - > + (('version is ' ~ docker_py_version ~ ' ') in capabilities_1.msg and 'Minimum version required is 5.0.3 ' in capabilities_1.msg) + or (('Docker API version is ' ~ docker_api_version ~ '. ') in capabilities_1.msg and 'Minimum version required is 1.41 ' in capabilities_1.msg) + when: docker_api_version is version('1.41', '<') or docker_py_version is version('5.0.3', '<') diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/placement.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/placement.yml new file mode 100644 index 000000000..30ed155b7 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/placement.yml @@ -0,0 +1,261 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-placement' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + + +#################################################################### +## placement.preferences ########################################### +#################################################################### + +- name: placement.preferences + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + preferences: + - spread: "node.labels.test" + register: placement_preferences_1 + ignore_errors: true + +- name: placement.preferences (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + preferences: + - spread: "node.labels.test" + register: placement_preferences_2 + ignore_errors: true + +- name: placement.preferences (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + preferences: + - spread: "node.labels.test2" + register: placement_preferences_3 + ignore_errors: true + +- name: placement.preferences (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + preferences: [] + register: placement_preferences_4 + ignore_errors: true + +- name: placement.preferences (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + preferences: [] + register: placement_preferences_5 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - placement_preferences_1 is changed + - placement_preferences_2 is not changed + - placement_preferences_3 is changed + - placement_preferences_4 is changed + - placement_preferences_5 is not changed + when: docker_api_version is version('1.27', '>=') and docker_py_version is version('2.4.0', '>=') +- assert: + that: + - placement_preferences_1 is failed + - "'Minimum version required' in placement_preferences_1.msg" + when: docker_api_version is version('1.27', '<') or docker_py_version is version('2.4.0', '<') + +#################################################################### +## placement.constraints ##################################################### +#################################################################### + +- name: placement.constraints + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + constraints: + - "node.role == manager" + register: constraints_1 + ignore_errors: true + +- name: placement.constraints (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + constraints: + - "node.role == manager" + register: constraints_2 + ignore_errors: true + +- name: placement.constraints (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + constraints: + - "node.role == worker" + register: constraints_3 + ignore_errors: true + +- name: placement.constraints (add) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + constraints: + - "node.role == worker" + - "node.label != non_existent_label" + register: constraints_4 + ignore_errors: true + +- name: placement.constraints (order idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + constraints: + - "node.label != non_existent_label" + - "node.role == worker" + register: constraints_5 + ignore_errors: true + +- name: placement.constraints (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + constraints: [] + register: constraints_6 + ignore_errors: true + +- name: placement.constraints (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + constraints: [] + register: constraints_7 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - constraints_1 is changed + - constraints_2 is not changed + - constraints_3 is changed + - constraints_4 is changed + - constraints_5 is not changed + - constraints_6 is changed + - constraints_7 is not changed + when: docker_api_version is version('1.27', '>=') and docker_py_version is version('2.4.0', '>=') +- assert: + that: + - constraints_1 is failed + - "'Minimum version required' in constraints_1.msg" + when: docker_api_version is version('1.27', '<') or docker_py_version is version('2.4.0', '<') + +#################################################################### +## placement.replicas_max_per_node ##################################################### +#################################################################### + +- name: placement.replicas_max_per_node + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + replicas_max_per_node: 1 + register: replicas_max_per_node_1 + ignore_errors: true + +- name: placement.replicas_max_per_node (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + replicas_max_per_node: 1 + register: replicas_max_per_node_2 + ignore_errors: true + +- name: placement.replicas_max_per_node (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + placement: + replicas_max_per_node: 2 + register: replicas_max_per_node_3 + ignore_errors: true + + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - replicas_max_per_node_1 is changed + - replicas_max_per_node_2 is not changed + - replicas_max_per_node_3 is changed + when: docker_api_version is version('1.40', '>=') and docker_py_version is version('4.4.3', '>=') +- assert: + that: + - replicas_max_per_node_1 is failed + - "'Minimum version required' in replicas_max_per_node_1.msg" + when: docker_api_version is version('1.40', '<') or docker_py_version is version('4.4.3', '<') diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/resources.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/resources.yml new file mode 100644 index 000000000..ac8917509 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/resources.yml @@ -0,0 +1,196 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-resources' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + +#################################################################### +## limits.cpus ##################################################### +#################################################################### + +- name: limits.cpus + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + limits: + cpus: 1 + register: limit_cpu_1 + +- name: limits.cpus (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + limits: + cpus: 1 + register: limit_cpu_2 + +- name: limits.cpus (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + limits: + cpus: 0.5 + register: limit_cpu_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - limit_cpu_1 is changed + - limit_cpu_2 is not changed + - limit_cpu_3 is changed + +################################################################### +## limits.memory ################################################## +################################################################### + +- name: limits.memory + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + limits: + memory: 64M + register: limit_memory_1 + +- name: limits.memory (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + limits: + memory: 64M + register: limit_memory_2 + +- name: limits.memory (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + limits: + memory: 32M + register: limit_memory_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - limit_memory_1 is changed + - limit_memory_2 is not changed + - limit_memory_3 is changed + +################################################################### +## reservations.cpus ############################################## +################################################################### + +- name: reserve_cpu + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + reservations: + cpus: 1 + register: reserve_cpu_1 + +- name: reserve_cpu (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + reservations: + cpus: 1 + register: reserve_cpu_2 + +- name: reserve_cpu (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + reservations: + cpus: 0.5 + register: reserve_cpu_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - reserve_cpu_1 is changed + - reserve_cpu_2 is not changed + - reserve_cpu_3 is changed + +################################################################### +## reservations.memory ############################################ +################################################################### + +- name: reservations.memory + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + reservations: + memory: 64M + register: reserve_memory_1 + +- name: reservations.memory (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + reservations: + memory: 64M + register: reserve_memory_2 + +- name: reservations.memory (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + reservations: + memory: 32M + register: reserve_memory_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - reserve_memory_1 is changed + - reserve_memory_2 is not changed + - reserve_memory_3 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/restart_config.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/restart_config.yml new file mode 100644 index 000000000..f80a44752 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/restart_config.yml @@ -0,0 +1,196 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-restart_config' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + +################################################################### +## restart_config.condition ####################################### +################################################################### + +- name: restart_config.condition + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + condition: "on-failure" + register: restart_policy_1 + +- name: restart_config.condition (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + condition: "on-failure" + register: restart_policy_2 + +- name: restart_config.condition (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + condition: "any" + register: restart_policy_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - restart_policy_1 is changed + - restart_policy_2 is not changed + - restart_policy_3 is changed + +################################################################### +## restart_config.max_attempts #################################### +################################################################### + +- name: restart_config.max_attempts + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + max_attempts: 1 + register: restart_policy_attempts_1 + +- name: restart_config.max_attempts (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + max_attempts: 1 + register: restart_policy_attempts_2 + +- name: restart_config.max_attempts (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + max_attempts: 2 + register: restart_policy_attempts_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - restart_policy_attempts_1 is changed + - restart_policy_attempts_2 is not changed + - restart_policy_attempts_3 is changed + +################################################################### +## restart_config.delay ########################################### +################################################################### + +- name: restart_config.delay + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + delay: 5s + register: restart_policy_delay_1 + +- name: restart_config.delay (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + delay: 5s + register: restart_policy_delay_2 + +- name: restart_config.delay (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + delay: 10s + register: restart_policy_delay_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - restart_policy_delay_1 is changed + - restart_policy_delay_2 is not changed + - restart_policy_delay_3 is changed + +################################################################### +## restart_config.window ########################################## +################################################################### + +- name: restart_config.window + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + window: 10s + register: restart_policy_window_1 + +- name: restart_config.window (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + window: 10s + register: restart_policy_window_2 + +- name: restart_config.window (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + restart_config: + window: 20s + register: restart_policy_window_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - restart_policy_window_1 is changed + - restart_policy_window_2 is not changed + - restart_policy_window_3 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/rollback_config.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/rollback_config.yml new file mode 100644 index 000000000..9035ffdbc --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/rollback_config.yml @@ -0,0 +1,342 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-rollback_config' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + +################################################################### +## rollback_config.delay ############################################ +################################################################### + +- name: rollback_config.delay + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + delay: 5s + register: rollback_config_delay_1 + ignore_errors: true + +- name: rollback_config.delay (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + delay: 5s + register: rollback_config_delay_2 + ignore_errors: true + +- name: rollback_config.delay (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + delay: 12s + register: rollback_config_delay_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - rollback_config_delay_1 is changed + - rollback_config_delay_2 is not changed + - rollback_config_delay_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_delay_1 is failed + - "'Minimum version required' in rollback_config_delay_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +## rollback_config.failure_action ################################### +################################################################### + +- name: rollback_config.failure_action + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + failure_action: "pause" + register: rollback_config_failure_action_1 + ignore_errors: true + +- name: rollback_config.failure_action (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + failure_action: "pause" + register: rollback_config_failure_action_2 + ignore_errors: true + +- name: rollback_config.failure_action (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + failure_action: "continue" + register: rollback_config_failure_action_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - rollback_config_failure_action_1 is changed + - rollback_config_failure_action_2 is not changed + - rollback_config_failure_action_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_failure_action_1 is failed + - "'Minimum version required' in rollback_config_failure_action_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +## rollback_config.max_failure_ratio ################################ +################################################################### + +- name: rollback_config.max_failure_ratio + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + max_failure_ratio: 0.25 + register: rollback_config_max_failure_ratio_1 + ignore_errors: true + +- name: rollback_config.max_failure_ratio (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + max_failure_ratio: 0.25 + register: rollback_config_max_failure_ratio_2 + ignore_errors: true + +- name: rollback_config.max_failure_ratio (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + max_failure_ratio: 0.50 + register: rollback_config_max_failure_ratio_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - rollback_config_max_failure_ratio_1 is changed + - rollback_config_max_failure_ratio_2 is not changed + - rollback_config_max_failure_ratio_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_max_failure_ratio_1 is failed + - "'Minimum version required' in rollback_config_max_failure_ratio_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +# rollback_config.monitor ########################################### +################################################################### + +- name: rollback_config.monitor + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + monitor: 10s + register: rollback_config_monitor_1 + ignore_errors: true + +- name: rollback_config.monitor (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + monitor: 10s + register: rollback_config_monitor_2 + ignore_errors: true + +- name: rollback_config.monitor (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + monitor: 60s + register: rollback_config_monitor_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - rollback_config_monitor_1 is changed + - rollback_config_monitor_2 is not changed + - rollback_config_monitor_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_monitor_1 is failed + - "'Minimum version required' in rollback_config_monitor_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +# rollback_config.order ############################################# +################################################################### + +- name: rollback_config.order + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + order: "start-first" + register: rollback_config_order_1 + ignore_errors: true + +- name: rollback_config.order (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + order: "start-first" + register: rollback_config_order_2 + ignore_errors: true + +- name: rollback_config.order (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + order: "stop-first" + register: rollback_config_order_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - rollback_config_order_1 is changed + - rollback_config_order_2 is not changed + - rollback_config_order_3 is changed + when: docker_api_version is version('1.29', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_order_1 is failed + - "'Minimum version required' in rollback_config_order_1.msg" + when: docker_api_version is version('1.29', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +## rollback_config.parallelism ###################################### +################################################################### + +- name: rollback_config.parallelism + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + parallelism: 2 + register: rollback_config_parallelism_1 + ignore_errors: true + +- name: rollback_config.parallelism (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + parallelism: 2 + register: rollback_config_parallelism_2 + ignore_errors: true + +- name: rollback_config.parallelism (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + rollback_config: + parallelism: 1 + register: rollback_config_parallelism_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - rollback_config_parallelism_1 is changed + - rollback_config_parallelism_2 is not changed + - rollback_config_parallelism_3 is changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') +- assert: + that: + - rollback_config_parallelism_1 is failed + - "'Minimum version required' in rollback_config_parallelism_1.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/secrets.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/secrets.yml new file mode 100644 index 000000000..2af5076fd --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/secrets.yml @@ -0,0 +1,461 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering container name + set_fact: + service_name: "{{ name_prefix ~ '-secrets' }}" + secret_name_1: "{{ name_prefix ~ '-secret-1' }}" + secret_name_2: "{{ name_prefix ~ '-secret-2' }}" + secret_name_3: "{{ name_prefix ~ '-secret-3' }}" + +- name: Registering container name + set_fact: + secret_names: "{{ secret_names + [secret_name_1, secret_name_2] }}" + +- docker_secret: + name: "{{ secret_name_1 }}" + data: "secret1" + state: "present" + register: "secret_result_1" + when: docker_py_version is version('2.1.0', '>=') + +- docker_secret: + name: "{{ secret_name_2 }}" + data: "secret2" + state: "present" + register: "secret_result_2" + when: docker_py_version is version('2.1.0', '>=') + +- docker_secret: + name: "{{ secret_name_3 }}" + data: "secret3" + state: "present" + rolling_versions: true + register: "secret_result_3" + when: docker_py_version is version('2.1.0', '>=') + +#################################################################### +## secrets ######################################################### +#################################################################### + +- name: secrets + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + filename: "/run/secrets/{{ secret_name_1 }}.txt" + register: secrets_1 + ignore_errors: true + +- name: secrets (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_name: "{{ secret_name_1 }}" + filename: "/run/secrets/{{ secret_name_1 }}.txt" + register: secrets_2 + ignore_errors: true + +- name: secrets (add) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + filename: "/run/secrets/{{ secret_name_1 }}.txt" + - secret_name: "{{ secret_name_2 }}" + filename: "/run/secrets/{{ secret_name_2 }}.txt" + register: secrets_3 + ignore_errors: true + +- name: secrets (add idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_name: "{{ secret_name_1 }}" + filename: "/run/secrets/{{ secret_name_1 }}.txt" + - secret_id: "{{ secret_result_2.secret_id|default('') }}" + secret_name: "{{ secret_name_2 }}" + filename: "/run/secrets/{{ secret_name_2 }}.txt" + register: secrets_4 + ignore_errors: true + +- name: secrets (add idempotency no id) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_name: "{{ secret_name_1 }}" + filename: "/run/secrets/{{ secret_name_1 }}.txt" + - secret_name: "{{ secret_name_2 }}" + filename: "/run/secrets/{{ secret_name_2 }}.txt" + register: secrets_5 + ignore_errors: true + +- name: secrets (order idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_name: "{{ secret_name_2 }}" + filename: "/run/secrets/{{ secret_name_2 }}.txt" + - secret_name: "{{ secret_name_1 }}" + filename: "/run/secrets/{{ secret_name_1 }}.txt" + register: secrets_6 + ignore_errors: true + +- name: secrets (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: [] + register: secrets_7 + ignore_errors: true + +- name: secrets (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: [] + register: secrets_8 + ignore_errors: true + +- name: rolling secrets + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_name: "{{ secret_name_3 }}_v1" + filename: "/run/secrets/{{ secret_name_3 }}.txt" + register: secrets_9 + ignore_errors: true + +- name: update rolling secret + docker_secret: + name: "{{ secret_name_3 }}" + data: "newsecret3" + state: "present" + rolling_versions: true + register: secrets_10 + when: docker_py_version is version('2.1.0', '>=') + ignore_errors: true + +- name: rolling secrets service update + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_name: "{{ secret_name_3 }}_v2" + filename: "/run/secrets/{{ secret_name_3 }}.txt" + register: secrets_11 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - secrets_1 is changed + - secrets_2 is not changed + - secrets_3 is changed + - secrets_4 is not changed + - secrets_5 is not changed + - secrets_6 is not changed + - secrets_7 is changed + - secrets_8 is not changed + - secrets_9 is changed + - secrets_10 is not failed + - secrets_11 is changed + when: docker_py_version is version('2.4.0', '>=') +- assert: + that: + - secrets_1 is failed + - "'Minimum version required' in secrets_1.msg" + when: docker_py_version is version('2.4.0', '<') + +#################################################################### +## secrets (uid) ################################################### +#################################################################### + +- name: secrets (uid int) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + uid: 1000 + register: secrets_1 + ignore_errors: true + +- name: secrets (uid int idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + uid: 1000 + register: secrets_2 + ignore_errors: true + +- name: secrets (uid int change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + uid: 1002 + register: secrets_3 + ignore_errors: true + +- name: secrets (uid str) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + uid: "1001" + register: secrets_4 + ignore_errors: true + +- name: secrets (uid str idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + uid: "1001" + register: secrets_5 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - secrets_1 is changed + - secrets_2 is not changed + - secrets_3 is changed + - secrets_4 is changed + - secrets_5 is not changed + when: docker_py_version is version('2.4.0', '>=') +- assert: + that: + - secrets_1 is failed + - "'Minimum version required' in secrets_1.msg" + when: docker_py_version is version('2.4.0', '<') + +#################################################################### +## secrets (gid) ################################################### +#################################################################### + +- name: secrets (gid int) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + gid: 1001 + register: secrets_1 + ignore_errors: true + +- name: secrets (gid int idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + gid: 1001 + register: secrets_2 + ignore_errors: true + +- name: secrets (gid int change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + gid: 1002 + register: secrets_3 + ignore_errors: true + +- name: secrets (gid str) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + gid: "1003" + register: secrets_4 + ignore_errors: true + +- name: secrets (gid str idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + gid: "1003" + register: secrets_5 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - secrets_1 is changed + - secrets_2 is not changed + - secrets_3 is changed + - secrets_4 is changed + - secrets_5 is not changed + when: docker_py_version is version('2.4.0', '>=') +- assert: + that: + - secrets_1 is failed + - "'Minimum version required' in secrets_1.msg" + when: docker_py_version is version('2.4.0', '<') + +#################################################################### +## secrets (mode) ################################################## +#################################################################### + +- name: secrets (mode) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + mode: 0600 + register: secrets_1 + ignore_errors: true + +- name: secrets (mode idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + mode: 0600 + register: secrets_2 + ignore_errors: true + +- name: secrets (mode change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_id: "{{ secret_result_1.secret_id|default('') }}" + secret_name: "{{ secret_name_1 }}" + mode: 0777 + register: secrets_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - secrets_1 is changed + - secrets_2 is not changed + - secrets_3 is changed + when: docker_py_version is version('2.4.0', '>=') +- assert: + that: + - secrets_1 is failed + - "'Minimum version required' in secrets_1.msg" + when: docker_py_version is version('2.4.0', '<') + +#################################################################### +#################################################################### +#################################################################### + +- name: Delete secrets + docker_secret: + name: "{{ secret_name }}" + state: absent + force: true + loop: + - "{{ secret_name_1 }}" + - "{{ secret_name_2 }}" + - "{{ secret_name_3 }}" + loop_control: + loop_var: secret_name + ignore_errors: true + when: docker_py_version is version('2.1.0', '>=') diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/update_config.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/update_config.yml new file mode 100644 index 000000000..fb335a036 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/tasks/tests/update_config.yml @@ -0,0 +1,350 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering service name + set_fact: + service_name: "{{ name_prefix ~ '-update_config' }}" + +- name: Registering service name + set_fact: + service_names: "{{ service_names + [service_name] }}" + +################################################################### +## update_config.delay ############################################ +################################################################### + +- name: update_config.delay + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + delay: 5s + register: update_delay_1 + +- name: update_config.delay (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + delay: 5s + register: update_delay_2 + +- name: update_config.delay (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + delay: 12s + register: update_delay_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - update_delay_1 is changed + - update_delay_2 is not changed + - update_delay_3 is changed + +################################################################### +## update_config.failure_action ################################### +################################################################### + +- name: update_config.failure_action + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + failure_action: "pause" + register: update_failure_action_1 + +- name: update_config.failure_action (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + failure_action: "pause" + register: update_failure_action_2 + +- name: update_config.failure_action (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + failure_action: "continue" + register: update_failure_action_3 + +- name: update_config.failure_action (rollback) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + failure_action: "rollback" + register: update_failure_action_4 + ignore_errors: true + +- name: update_config.failure_action (rollback idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + failure_action: "rollback" + register: update_failure_action_5 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - update_failure_action_1 is changed + - update_failure_action_2 is not changed + - update_failure_action_3 is changed + +- assert: + that: + - update_failure_action_4 is changed + - update_failure_action_5 is not failed + - update_failure_action_5 is not changed + when: docker_api_version is version('1.28', '>=') and docker_py_version is version('3.5.0', '>=') + +- assert: + that: + - update_failure_action_4 is failed + - "'Minimum version required' in update_failure_action_4.msg" + when: docker_api_version is version('1.28', '<') or docker_py_version is version('3.5.0', '<') + +################################################################### +## update_config.max_failure_ratio ################################ +################################################################### + +- name: update_config.max_failure_ratio + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + max_failure_ratio: 0.25 + register: update_max_failure_ratio_1 + ignore_errors: true + +- name: update_config.max_failure_ratio (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + max_failure_ratio: 0.25 + register: update_max_failure_ratio_2 + ignore_errors: true + +- name: update_config.max_failure_ratio (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + max_failure_ratio: 0.50 + register: update_max_failure_ratio_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - update_max_failure_ratio_1 is changed + - update_max_failure_ratio_2 is not changed + - update_max_failure_ratio_3 is changed + when: docker_py_version is version('2.1.0', '>=') +- assert: + that: + - update_max_failure_ratio_1 is failed + - "'Minimum version required' in update_max_failure_ratio_1.msg" + when: docker_py_version is version('2.1.0', '<') + +################################################################### +# update_config.monitor ########################################### +################################################################### + +- name: update_config.monitor + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + monitor: 10s + register: update_monitor_1 + ignore_errors: true + +- name: update_config.monitor (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + monitor: 10s + register: update_monitor_2 + ignore_errors: true + +- name: update_config.monitor (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + monitor: 60s + register: update_monitor_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - update_monitor_1 is changed + - update_monitor_2 is not changed + - update_monitor_3 is changed + when: docker_py_version is version('2.1.0', '>=') +- assert: + that: + - update_monitor_1 is failed + - "'Minimum version required' in update_monitor_1.msg" + when: docker_py_version is version('2.1.0', '<') + +################################################################### +# update_config.order ############################################# +################################################################### + +- name: update_config.order + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + order: "start-first" + register: update_order_1 + ignore_errors: true + +- name: update_config.order (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + order: "start-first" + register: update_order_2 + ignore_errors: true + +- name: update_config.order (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + order: "stop-first" + register: update_order_3 + ignore_errors: true + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - update_order_1 is changed + - update_order_2 is not changed + - update_order_3 is changed + when: docker_api_version is version('1.29', '>=') and docker_py_version is version('2.7.0', '>=') +- assert: + that: + - update_order_1 is failed + - "'Minimum version required' in update_order_1.msg" + when: docker_api_version is version('1.29', '<') or docker_py_version is version('2.7.0', '<') + +################################################################### +## update_config.parallelism ###################################### +################################################################### + +- name: update_config.parallelism + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + parallelism: 2 + register: update_parallelism_1 + +- name: update_config.parallelism (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + parallelism: 2 + register: update_parallelism_2 + +- name: update_config.parallelism (change) + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + resolve_image: false + command: '/bin/sh -v -c "sleep 10m"' + update_config: + parallelism: 1 + register: update_parallelism_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: false + +- assert: + that: + - update_parallelism_1 is changed + - update_parallelism_2 is not changed + - update_parallelism_3 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/vars/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/vars/main.yml new file mode 100644 index 000000000..836ee41c4 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service/vars/main.yml @@ -0,0 +1,60 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +service_expected_output: + args: [sleep, '1800'] + cap_add: null + cap_drop: null + configs: null + constraints: null + container_labels: null + command: null + dns: null + dns_options: null + dns_search: null + endpoint_mode: vip + env: null + force_update: null + groups: null + healthcheck: null + healthcheck_disabled: null + hostname: null + hosts: null + image: "{{ docker_test_image_busybox }}" + labels: null + limit_cpu: null + limit_memory: null + log_driver: null + log_driver_options: null + mode: global + mounts: null + networks: null + secrets: null + stop_grace_period: null + stop_signal: null + placement_preferences: null + publish: + - {mode: null, protocol: tcp, published_port: 60001, target_port: 60001} + - {mode: null, protocol: udp, published_port: 60001, target_port: 60001} + read_only: null + replicas: null + replicas_max_per_node: null + reserve_cpu: null + reserve_memory: null + restart_policy: null + restart_policy_attempts: null + restart_policy_delay: null + restart_policy_window: null + rollback_config: null + tty: null + update_delay: null + update_failure_action: null + update_max_failure_ratio: null + update_monitor: null + update_order: null + update_parallelism: null + user: null + working_dir: null + init: null diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/aliases new file mode 100644 index 000000000..fc581d544 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/3 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/tasks/main.yml new file mode 100644 index 000000000..cd112a897 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: test_docker_swarm_service_info.yml + when: docker_py_version is version('2.0.0', '>=') and docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_swarm_service_info tests!" + when: not(docker_py_version is version('2.0.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/tasks/test_docker_swarm_service_info.yml b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/tasks/test_docker_swarm_service_info.yml new file mode 100644 index 000000000..ee191138b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_swarm_service_info/tasks/test_docker_swarm_service_info.yml @@ -0,0 +1,85 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Generate service base name + set_fact: + service_base_name: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + +- name: Registering service names + set_fact: + service_name: "{{ service_base_name ~ '-1' }}" + +- block: + - name: Make sure we're not already using Docker swarm + docker_swarm: + state: absent + force: true + + - name: Try to get docker_swarm_service_info when docker is not running in swarm mode + docker_swarm_service_info: + name: "{{ service_name }}" + ignore_errors: true + register: output + + - name: assert failure when called when swarm is not in use or not run on manager node + assert: + that: + - 'output is failed' + - 'output.msg == "Error running docker swarm module: must run on swarm manager node"' + + - name: Create a Swarm cluster + docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" + register: output + + - name: Create services + docker_swarm_service: + name: "{{ service_name }}" + image: "{{ docker_test_image_alpine }}" + + - name: Try to get docker_swarm_service_info for a single service + docker_swarm_service_info: + name: "{{ service_name }}" + register: output + + - name: assert reading reading service info + assert: + that: + - 'output.exists == true' + - 'output.service.ID is string' + - 'output.service.Spec.Name == service_name' + + - name: Create random name + set_fact: + random_service_name: "{{ 'random-service-%0x' % ((2**32) | random) }}" + + - name: Try to get docker_swarm_service_info using random service name as parameter + docker_swarm_service_info: + name: "{{ random_service_name }}" + register: output + + - name: assert reading reading service info + assert: + that: + - 'output.service is none' + - 'output.exists == false' + + always: + - name: Remove services + docker_swarm_service: + name: "{{ service_name }}" + state: absent + ignore_errors: true + + - name: Remove swarm + docker_swarm: + state: absent + force: true + + when: docker_py_version is version('2.0.2', '>=') and docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_swarm_service_info tests!" + when: not(docker_py_version is version('2.0.2', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_volume/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_volume/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_volume/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_volume/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_volume/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_volume/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/main.yml new file mode 100644 index 000000000..b356e5618 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/main.yml @@ -0,0 +1,34 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Create random name prefix + set_fact: + name_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + vnames: [] + +- debug: + msg: "Using name prefix {{ name_prefix }}" + +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + + always: + - name: "Make sure all volumes are removed" + docker_volume: + name: "{{ item }}" + state: absent + with_items: "{{ vnames }}" + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_volume tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/run-test.yml b/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/run-test.yml new file mode 100644 index 000000000..65853ddd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/run-test.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/tests/basic.yml b/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/tests/basic.yml new file mode 100644 index 000000000..b65bbd9db --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_volume/tasks/tests/basic.yml @@ -0,0 +1,181 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Registering volume name + set_fact: + vname: "{{ name_prefix ~ '-basic' }}" +- name: Registering container name + set_fact: + vnames: "{{ vnames + [vname] }}" + +#################################################################### +## basic ########################################################### +#################################################################### + +- name: Create a volume + docker_volume: + name: "{{ vname }}" + register: create_1 + +- name: Create a volume (idempotency) + docker_volume: + name: "{{ vname }}" + register: create_2 + +- name: "Create a volume (recreate: options-changed)" + docker_volume: + name: "{{ vname }}" + recreate: options-changed + register: create_3 + +- name: "Create a volume (recreate: always)" + docker_volume: + name: "{{ vname }}" + recreate: always + register: create_4 + +- name: Remove a volume + docker_volume: + name: "{{ vname }}" + state: absent + register: absent_1 + +- name: Remove a volume (idempotency) + docker_volume: + name: "{{ vname }}" + state: absent + register: absent_2 + +- assert: + that: + - create_1 is changed + - create_2 is not changed + - create_3 is not changed + - create_4 is changed + - absent_1 is changed + - absent_2 is not changed + +#################################################################### +## driver_options ################################################## +#################################################################### + +- name: Create a volume with options + docker_volume: + name: "{{ vname }}" + driver: local + driver_options: + type: tempfs + device: tmpfs + o: size=100m,uid=1000 + register: driver_options_1 + +- name: Create a volume with options (idempotency) + docker_volume: + name: "{{ vname }}" + driver: local + driver_options: + type: tempfs + device: tmpfs + o: size=100m,uid=1000 + register: driver_options_2 + +- name: Create a volume with options (changed) + docker_volume: + name: "{{ vname }}" + driver: local + driver_options: + type: tempfs + device: tmpfs + o: size=200m,uid=1000 + register: driver_options_3 + +- name: "Create a volume with options (changed, recreate: options-changed)" + docker_volume: + name: "{{ vname }}" + driver: local + driver_options: + type: tempfs + device: tmpfs + o: size=200m,uid=1000 + recreate: options-changed + register: driver_options_4 + +- name: Cleanup + docker_volume: + name: "{{ vname }}" + state: absent + +- assert: + that: + - driver_options_1 is changed + - driver_options_2 is not changed + - driver_options_3 is not changed + - driver_options_4 is changed + +#################################################################### +## labels ########################################################## +#################################################################### + +- name: Create a volume with labels + docker_volume: + name: "{{ vname }}" + labels: + ansible.test.1: hello + ansible.test.2: world + register: driver_labels_1 + +- name: Create a volume with labels (idempotency) + docker_volume: + name: "{{ vname }}" + labels: + ansible.test.2: world + ansible.test.1: hello + register: driver_labels_2 + +- name: Create a volume with labels (less) + docker_volume: + name: "{{ vname }}" + labels: + ansible.test.1: hello + register: driver_labels_3 + +- name: "Create a volume with labels (less, recreate: options-changed)" + docker_volume: + name: "{{ vname }}" + labels: + ansible.test.1: hello + recreate: options-changed + register: driver_labels_4 + +- name: Create a volume with labels (more) + docker_volume: + name: "{{ vname }}" + labels: + ansible.test.1: hello + ansible.test.3: ansible + register: driver_labels_5 + +- name: "Create a volume with labels (more, recreate: options-changed)" + docker_volume: + name: "{{ vname }}" + labels: + ansible.test.1: hello + ansible.test.3: ansible + recreate: options-changed + register: driver_labels_6 + +- name: Cleanup + docker_volume: + name: "{{ vname }}" + state: absent + +- assert: + that: + - driver_labels_1 is changed + - driver_labels_2 is not changed + - driver_labels_3 is not changed + - driver_labels_4 is not changed + - driver_labels_5 is not changed + - driver_labels_6 is changed diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/aliases b/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/tasks/main.yml new file mode 100644 index 000000000..09cb84d73 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/docker_volume_info/tasks/main.yml @@ -0,0 +1,77 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Create random volume name + set_fact: + cname: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + + - name: Make sure volume is not there + docker_volume: + name: "{{ cname }}" + state: absent + + - name: Inspect a non-present volume + docker_volume_info: + name: "{{ cname }}" + register: result + + - assert: + that: + - "not result.exists" + - "'volume' in result" + - "result.volume is none" + + - name: Make sure volume exists + docker_volume: + name: "{{ cname }}" + + - name: Inspect a present volume + docker_volume_info: + name: "{{ cname }}" + register: result + - name: Dump docker_volume_info result + debug: var=result + + - name: "Comparison: use 'docker volume inspect'" + command: docker volume inspect "{{ cname }}" + register: docker_volume_inspect + ignore_errors: true + - block: + - set_fact: + docker_volume_inspect_result: "{{ docker_volume_inspect.stdout | from_json }}" + - name: Dump docker volume inspect result + debug: var=docker_volume_inspect_result + when: docker_volume_inspect is not failed + + - name: Cleanup + docker_volume: + name: "{{ cname }}" + state: absent + + - assert: + that: + - result.exists + - "'volume' in result" + - "result.volume" + + - assert: + that: + - "result.volume == docker_volume_inspect_result[0]" + when: docker_volume_inspect is not failed + - assert: + that: + - "'is too new. Maximum supported API version is' in docker_volume_inspect.stderr" + when: docker_volume_inspect is failed + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_volume_info tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/aliases b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/aliases new file mode 100644 index 000000000..2e1acc0ad --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/files/nginx.conf b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/files/nginx.conf new file mode 100644 index 000000000..50f92c29f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/files/nginx.conf @@ -0,0 +1,50 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +user root; + +events { + worker_connections 16; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + error_log /dev/stdout info; + access_log /dev/stdout; + + server { + listen *:5000 ssl; + server_name daemon-tls.ansible.com; + server_name_in_redirect on; + + ssl_protocols TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256'; + ssl_ecdh_curve X25519:secp521r1:secp384r1; + ssl_prefer_server_ciphers on; + ssl_certificate /etc/nginx/cert.pem; + ssl_certificate_key /etc/nginx/cert.key; + + location / { + proxy_pass http://unix:/var/run/docker.sock:/; + + client_max_body_size 0; + chunked_transfer_encoding on; + } + } + + server { + listen *:6000; + server_name daemon.ansible.com; + server_name_in_redirect on; + + location / { + proxy_pass http://unix:/var/run/docker.sock:/; + + client_max_body_size 0; + chunked_transfer_encoding on; + } + } +} diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/filter_plugins/filter_attr.py b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/filter_plugins/filter_attr.py new file mode 100644 index 000000000..f821b7e72 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/filter_plugins/filter_attr.py @@ -0,0 +1,20 @@ +# Copyright (c) 2022 Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def sanitize_host_info(data): + data = data.copy() + for key in ('SystemTime', 'NFd', 'NGoroutines', ): + data.pop(key, None) + return data + + +class FilterModule: + def filters(self): + return { + 'sanitize_host_info': sanitize_host_info, + } diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/meta/main.yml new file mode 100644 index 000000000..e7ff3d68b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/meta/main.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_openssl + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/tasks/main.yml new file mode 100644 index 000000000..abbb02956 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/tasks/main.yml @@ -0,0 +1,195 @@ +--- +# Copyright (c) 2022 Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Create random nginx frontend name + set_fact: + daemon_nginx_frontend: '{{ "ansible-docker-test-daemon-frontend-%0x" % ((2**32) | random) }}' + +- block: + - name: Create volume for config files + docker_volume: + name: '{{ daemon_nginx_frontend }}' + state: present + + - name: Create container for nginx frontend for daemon + docker_container: + state: stopped + name: '{{ daemon_nginx_frontend }}' + image: "{{ docker_test_image_registry_nginx }}" + volumes: + - '{{ daemon_nginx_frontend }}:/etc/nginx/' + - '/var/run/docker.sock:/var/run/docker.sock' + network_mode: '{{ current_container_network_ip | default(omit, true) }}' + networks: >- + {{ + [dict([['name', current_container_network_ip]])] + if current_container_network_ip not in ['', 'bridge'] else omit + }} + register: nginx_container + + - name: Copy config files + copy: + src: "{{ item }}" + dest: "{{ remote_tmp_dir }}/{{ item }}" + mode: "0644" + loop: + - nginx.conf + + - name: Copy static files into volume + docker_container_copy_into: + container: '{{ daemon_nginx_frontend }}' + path: '{{ remote_tmp_dir }}/{{ item }}' + container_path: '/etc/nginx/{{ item }}' + owner_id: 0 + group_id: 0 + loop: + - nginx.conf + register: can_copy_files + ignore_errors: true + + - when: can_copy_files is not failed + block: + + - name: Create private keys + community.crypto.openssl_privatekey: + path: '{{ remote_tmp_dir }}/{{ item }}.key' + type: ECC + curve: secp256r1 + force: true + loop: + - cert + - ca + + - name: Create CSR for CA certificate + community.crypto.openssl_csr: + path: '{{ remote_tmp_dir }}/ca.csr' + privatekey_path: '{{ remote_tmp_dir }}/ca.key' + basic_constraints: + - 'CA:TRUE' + basic_constraints_critical: true + + - name: Create CA certificate + community.crypto.x509_certificate: + path: '{{ remote_tmp_dir }}/ca.pem' + csr_path: '{{ remote_tmp_dir }}/ca.csr' + privatekey_path: '{{ remote_tmp_dir }}/ca.key' + provider: selfsigned + + - name: Create CSR for frontend certificate + community.crypto.openssl_csr: + path: '{{ remote_tmp_dir }}/cert.csr' + privatekey_path: '{{ remote_tmp_dir }}/cert.key' + subject_alt_name: + - DNS:daemon-tls.ansible.com + + - name: Create frontend certificate + community.crypto.x509_certificate: + path: '{{ remote_tmp_dir }}/cert.pem' + csr_path: '{{ remote_tmp_dir }}/cert.csr' + privatekey_path: '{{ remote_tmp_dir }}/cert.key' + ownca_path: '{{ remote_tmp_dir }}/ca.pem' + ownca_privatekey_path: '{{ remote_tmp_dir }}/ca.key' + provider: ownca + + - name: Copy dynamic files into volume + docker_container_copy_into: + container: '{{ daemon_nginx_frontend }}' + path: '{{ remote_tmp_dir }}/{{ item }}' + container_path: '/etc/nginx/{{ item }}' + owner_id: 0 + group_id: 0 + loop: + - ca.pem + - cert.pem + - cert.key + + - name: Start nginx frontend for daemon + docker_container: + name: '{{ daemon_nginx_frontend }}' + state: started + register: nginx_container + + - name: Output nginx container network settings + debug: + var: nginx_container.container.NetworkSettings + + - name: Get proxied daemon URLs + set_fact: + docker_daemon_frontend_https: "https://{{ nginx_container.container.NetworkSettings.Networks[current_container_network_ip].IPAddress if current_container_network_ip else nginx_container.container.NetworkSettings.IPAddress }}:5000" + docker_daemon_frontend_http: "http://{{ nginx_container.container.NetworkSettings.Networks[current_container_network_ip].IPAddress if current_container_network_ip else nginx_container.container.NetworkSettings.IPAddress }}:6000" + + - name: Wait for registry frontend + uri: + url: '{{ docker_daemon_frontend_http }}/version' + register: result + until: result is success + retries: 5 + delay: 1 + + - name: Get docker daemon information directly + docker_host_info: + register: output_direct + + - name: Show direct host info + debug: + var: output_direct.host_info | sanitize_host_info + + - name: Get docker daemon information via HTTP + docker_host_info: + docker_host: '{{ docker_daemon_frontend_http }}' + register: output_http + + - name: Show HTTP host info + debug: + var: output_http.host_info | sanitize_host_info + + - name: Check that information matches + assert: + that: + - (output_direct.host_info | sanitize_host_info) == (output_http.host_info | sanitize_host_info) + + - name: Get docker daemon information via HTTPS + docker_host_info: + docker_host: '{{ docker_daemon_frontend_https }}' + tls_hostname: daemon-tls.ansible.com + ca_cert: '{{ remote_tmp_dir }}/ca.pem' + tls: true + validate_certs: true + register: output_https + + - name: Show HTTPS host info + debug: + var: output_https.host_info | sanitize_host_info + + - name: Check that information matches + assert: + that: + - (output_direct.host_info | sanitize_host_info) == (output_https.host_info | sanitize_host_info) + + always: + - command: docker logs {{ daemon_nginx_frontend }} + register: output + ignore_errors: true + - debug: + var: output.stdout_lines + ignore_errors: true + + - name: Remove container + docker_container: + state: absent + name: '{{ daemon_nginx_frontend }}' + force_kill: true + ignore_errors: true + + - name: Remove volume + docker_volume: + name: '{{ daemon_nginx_frontend }}' + state: absent + ignore_errors: true diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/vars/main.yml b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/vars/main.yml new file mode 100644 index 000000000..e4eafc24e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_connection_tests/vars/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_test_image_digest_v1: e004c2cc521c95383aebb1fb5893719aa7a8eae2e7a71f316a4410784edb00a9 +docker_test_image_digest_v2: ee44b399df993016003bf5466bd3eeb221305e9d0fa831606bc7902d149c775b +docker_test_image_digest_base: quay.io/ansible/docker-test-containers +docker_test_image_hello_world: quay.io/ansible/docker-test-containers:hello-world +docker_test_image_hello_world_base: quay.io/ansible/docker-test-containers +docker_test_image_busybox: quay.io/ansible/docker-test-containers:busybox +docker_test_image_alpine: quay.io/ansible/docker-test-containers:alpine3.8 +docker_test_image_alpine_different: quay.io/ansible/docker-test-containers:alpine3.7 +docker_test_image_registry_nginx: quay.io/ansible/docker-test-containers:nginx-alpine +docker_test_image_registry: registry:2.6.1 diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/aliases b/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/aliases new file mode 100644 index 000000000..116ebd84e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/aliases @@ -0,0 +1,9 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive +needs/root +skip/docker # we need a VM, and not a container +skip/alpine # for some reason, SSH has problems with Alpine VMs diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/meta/main.yml new file mode 100644 index 000000000..6fdc1c8ec --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_paramiko diff --git a/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/tasks/main.yml new file mode 100644 index 000000000..94554f716 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/generic_ssh_connection/tasks/main.yml @@ -0,0 +1,90 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Get docker daemon information directly + docker_host_info: + register: output + +- name: Make sure we got information + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + +- name: Show contents of ~/.ssh + command: ls -lah ~/.ssh + ignore_errors: true + +- name: Recover home directory on remote + command: echo $HOME + register: remote_home + +- name: Print remote home directory + debug: + var: remote_home.stdout + +- name: Create SSH config + copy: + dest: "{{ remote_home.stdout }}/.ssh/config" + mode: '0600' + content: | + Host localhost + User root + IdentityFile ~/.ssh/id_rsa + +- name: Get docker daemon information via ssh (paramiko) to localhost + docker_host_info: + docker_host: "ssh://root@localhost" + register: output + ignore_errors: true + +- name: Make sure we got information + assert: + that: + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + when: output is succeeded or 'Failed to import the required Python library (paramiko)' not in output.msg + # Sometimes paramiko being installed isn't enough: importing it can fail + # due to 'ImportError: No module named x25519' when it executes + # `from cryptography.hazmat.primitives.asymmetric.x25519 import ...`. + +- name: Get docker daemon information via ssh (OpenSSH) to localhost + docker_host_info: + docker_host: "ssh://root@localhost" + use_ssh_client: true + register: output + ignore_errors: true + +- name: Make sure we got information + assert: + that: + - output is succeeded + - 'output.host_info.Name is string' + - 'output.containers is not defined' + - 'output.networks is not defined' + - 'output.volumes is not defined' + - 'output.images is not defined' + - 'output.disk_usage is not defined' + when: docker_py_version is version('4.4.0', '>=') + +- name: Make sure we got information + assert: + that: + - output is failed + - "'use_ssh_client=True requires Docker SDK for Python 4.4.0 or newer' in output.msg" + when: docker_py_version is version('4.4.0', '<') diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/aliases b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/aliases new file mode 100644 index 000000000..1485e0b2e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/4 +destructive +needs/root diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/inventory_1.docker.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/inventory_1.docker.yml new file mode 100644 index 000000000..83fd6260a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/inventory_1.docker.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +plugin: community.docker.docker_containers +docker_host: unix://var/run/docker.sock diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/inventory_2.docker.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/inventory_2.docker.yml new file mode 100644 index 000000000..983495071 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/inventory_2.docker.yml @@ -0,0 +1,11 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +plugin: community.docker.docker_containers +docker_host: unix://var/run/docker.sock +connection_type: ssh +verbose_output: true +add_legacy_groups: true +default_ip: 1.2.3.4 diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/docker_cleanup.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/docker_cleanup.yml new file mode 100644 index 000000000..6f2a3b6c5 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/docker_cleanup.yml @@ -0,0 +1,26 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local + gather_facts: true + tasks: + - name: remove docker containers + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + loop: + - ansible-docker-test-docker-inventory-container-1 + - ansible-docker-test-docker-inventory-container-2 + + - name: remove docker pagkages + action: "{{ ansible_facts.pkg_mgr }}" + args: + name: + - docker + - docker-ce + - docker-ce-cli + state: absent diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/docker_setup.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/docker_setup.yml new file mode 100644 index 000000000..0c1f33685 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/docker_setup.yml @@ -0,0 +1,26 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local + vars: + docker_skip_cleanup: true + + tasks: + - name: Setup docker + import_role: + name: setup_docker + + - name: Start containers + docker_container: + name: "{{ item.name }}" + image: "{{ docker_test_image_alpine }}" + state: started + command: '/bin/sh -c "sleep 10m"' + published_ports: + - 22/tcp + loop: + - name: ansible-docker-test-docker-inventory-container-1 + - name: ansible-docker-test-docker-inventory-container-2 diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/test_inventory_1.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/test_inventory_1.yml new file mode 100644 index 000000000..c0f28f57d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/test_inventory_1.yml @@ -0,0 +1,40 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local # otherwise Ansible will complain that it cannot connect via ssh to 127.0.0.1:22 + gather_facts: false + tasks: + - name: Show all groups + debug: + var: groups + - name: Make sure that the default groups are there, but no others + assert: + that: + - groups.all | length >= 2 + - groups.ungrouped | length >= 2 + - groups | length == 2 + +- hosts: all + gather_facts: false + tasks: + - when: + # When the integration tests are run inside a docker container, there + # will be other containers. + - inventory_hostname.startswith('ansible-docker-test-docker-inventory-container-') + block: + + - name: Run raw command + raw: ls / + register: output + + - name: Check whether we have some directories we expect in the output + assert: + that: + - "'bin' in output.stdout_lines" + - "'dev' in output.stdout_lines" + - "'lib' in output.stdout_lines" + - "'proc' in output.stdout_lines" + - "'sys' in output.stdout_lines" diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/test_inventory_2.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/test_inventory_2.yml new file mode 100644 index 000000000..28f8dc008 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/playbooks/test_inventory_2.yml @@ -0,0 +1,49 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local # otherwise Ansible will complain that it cannot connect via ssh to 127.0.0.1:22 + gather_facts: false + tasks: + - name: Show all groups + debug: + var: groups + - name: Load variables + include_vars: ../../setup_docker/vars/main.yml + - name: Make sure that the expected groups are there + assert: + that: + - groups.all | length >= 2 + - groups.ungrouped | length >= 0 + - groups.running | length >= 2 + - groups.stopped | length >= 0 + - groups['image_' ~ docker_test_image_alpine] | length == 2 + - groups['ansible-docker-test-docker-inventory-container-1'] | length == 1 + - groups['ansible-docker-test-docker-inventory-container-2'] | length == 1 + - groups['unix://var/run/docker.sock'] | length >= 2 + - groups | length >= 12 + # The four additional groups are IDs and short IDs of the containers. + # When the integration tests are run inside a docker container, there + # will be more groups (for the additional container(s)). + +- hosts: all + # We don't really want to connect to the nodes, since we have no SSH daemon running on them + connection: local + vars: + ansible_python_interpreter: "{{ ansible_playbook_python }}" + gather_facts: false + tasks: + - name: Show all variables + debug: + var: hostvars[inventory_hostname] + - name: Make sure SSH is set up + assert: + that: + - ansible_ssh_host == '1.2.3.4' + - ansible_ssh_port == docker_networksettings.Ports['22/tcp'][0].HostPort + when: + # When the integration tests are run inside a docker container, there + # will be other containers. + - inventory_hostname.startswith('ansible-docker-test-docker-inventory-container-') diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/runme.sh b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/runme.sh new file mode 100755 index 000000000..acc1d5f45 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_containers/runme.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -n "$DEBUG" || -n "$ANSIBLE_DEBUG" ]] && set -x + +set -euo pipefail + +cleanup() { + echo "Cleanup" + ansible-playbook playbooks/docker_cleanup.yml + echo "Done" +} + +trap cleanup INT TERM EXIT + +echo "Setup" +ANSIBLE_ROLES_PATH=.. ansible-playbook playbooks/docker_setup.yml + +echo "Test docker_containers inventory 1" +ansible-playbook -i inventory_1.docker.yml playbooks/test_inventory_1.yml + +echo "Test docker_containers inventory 2" +ansible-playbook -i inventory_2.docker.yml playbooks/test_inventory_2.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/aliases b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/aliases new file mode 100644 index 000000000..956df459f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/aliases @@ -0,0 +1,8 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/2 +disabled +destructive +needs/root diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/docker-machine b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/docker-machine new file mode 100644 index 000000000..aad9e5fe2 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/docker-machine @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Mock Docker Machine wrapper for testing purposes + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +[ "$MOCK_ERROR_IN" == "$1" ] && echo >&2 "Mock Docker Machine error" && exit 1 +case $1 in + env) + cat <<'EOF' +export DOCKER_TLS_VERIFY="1" +export DOCKER_HOST="tcp://134.209.204.160:2376" +export DOCKER_CERT_PATH="/root/.docker/machine/machines/routinator" +export DOCKER_MACHINE_NAME="routinator" +# Run this command to configure your shell: +# eval $(docker-machine env --shell=bash routinator) +EOF + ;; + + *) + /usr/bin/docker-machine $* + ;; +esac diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_1.docker_machine.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_1.docker_machine.yml new file mode 100644 index 000000000..f8fc6b0c3 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_1.docker_machine.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +plugin: community.docker.docker_machine diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_2.docker_machine.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_2.docker_machine.yml new file mode 100644 index 000000000..817c55789 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_2.docker_machine.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +plugin: community.docker.docker_machine +daemon_env: require diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_3.docker_machine.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_3.docker_machine.yml new file mode 100644 index 000000000..95a6e8273 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/inventory_3.docker_machine.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +plugin: community.docker.docker_machine +daemon_env: optional diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/pre-setup.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/pre-setup.yml new file mode 100644 index 000000000..3c6c1367c --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/pre-setup.yml @@ -0,0 +1,22 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local + tasks: + - name: Setup docker + include_role: + name: setup_docker + + # There seems to be no better way to install docker-machine. At least I couldn't find any packages for RHEL7/8. + - name: Download docker-machine binary + vars: + docker_machine_version: "0.16.1" + get_url: + url: "https://github.com/docker/machine/releases/download/v{{ docker_machine_version }}/docker-machine-{{ ansible_system }}-{{ ansible_userspace_architecture }}" + dest: /tmp/docker-machine + - name: Install docker-machine binary + command: install /tmp/docker-machine /usr/bin/docker-machine + become: true diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/setup.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/setup.yml new file mode 100644 index 000000000..02d9ad4a8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/setup.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local + tasks: + - name: Request Docker Machine to use this machine as a generic VM + command: "docker-machine --debug create \ + --driver generic \ + --generic-ip-address=localhost \ + --generic-ssh-key {{ lookup('env', 'HOME') }}/.ssh/id_rsa \ + --generic-ssh-user root \ + vm" diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/teardown.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/teardown.yml new file mode 100644 index 000000000..8fb6fbdf0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/teardown.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local + tasks: + - name: Request Docker Machine to remove this machine as a generic VM + command: "docker-machine rm vm -f" diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/test_inventory_1.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/test_inventory_1.yml new file mode 100644 index 000000000..fb58718df --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/playbooks/test_inventory_1.yml @@ -0,0 +1,55 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + gather_facts: false + tasks: + - name: sanity check Docker Machine output + vars: + dm_ls_format: !unsafe '{{.Name}} | {{.DriverName}} | {{.State}} | {{.URL}} | {{.Error}}' + success_regex: "^vm | [^|]+ | Running | tcp://.+ |$" + command: docker-machine ls --format '{{ dm_ls_format }}' + register: result + failed_when: result.rc != 0 or result.stdout is not match(success_regex) + + - name: verify Docker Machine ip + command: docker-machine ip vm + register: result + failed_when: result.rc != 0 or result.stdout != hostvars['vm'].ansible_host + + - name: verify Docker Machine env + command: docker-machine env --shell=sh vm + register: result + + - debug: var=result.stdout + + - assert: + that: + - "'DOCKER_TLS_VERIFY=\"{{ hostvars['vm'].dm_DOCKER_TLS_VERIFY }}\"' in result.stdout" + - "'DOCKER_HOST=\"{{ hostvars['vm'].dm_DOCKER_HOST }}\"' in result.stdout" + - "'DOCKER_CERT_PATH=\"{{ hostvars['vm'].dm_DOCKER_CERT_PATH }}\"' in result.stdout" + - "'DOCKER_MACHINE_NAME=\"{{ hostvars['vm'].dm_DOCKER_MACHINE_NAME }}\"' in result.stdout" + +- hosts: vm + gather_facts: false + tasks: + - name: do something to verify that accept-new ssh setting was applied by the docker-machine inventory plugin + raw: uname -a + register: result + + - debug: var=result.stdout + +- hosts: 127.0.0.1 + gather_facts: false + environment: + DOCKER_CERT_PATH: "{{ hostvars['vm'].dm_DOCKER_CERT_PATH }}" + DOCKER_HOST: "{{ hostvars['vm'].dm_DOCKER_HOST }}" + DOCKER_MACHINE_NAME: "{{ hostvars['vm'].dm_DOCKER_MACHINE_NAME }}" + DOCKER_TLS_VERIFY: "{{ hostvars['vm'].dm_DOCKER_TLS_VERIFY }}" + tasks: + - name: run a Docker container on the target Docker Machine host to verify that Docker daemon connection settings from the docker-machine inventory plugin work as expected + docker_container: + name: test + image: "{{ docker_test_image_hello_world }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/runme.sh b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/runme.sh new file mode 100755 index 000000000..b39a08c41 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/runme.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +SCRIPT_DIR=$(dirname "$0") + +echo "Who am I: $(whoami)" +echo "Home: ${HOME}" +echo "PWD: $(pwd)" +echo "Script dir: ${SCRIPT_DIR}" + +# restrict Ansible just to our inventory plugin, to prevent inventory data being matched by the test but being provided +# by some other dynamic inventory provider +export ANSIBLE_INVENTORY_ENABLED=docker_machine + +[[ -n "$DEBUG" || -n "$ANSIBLE_DEBUG" ]] && set -x + +set -euo pipefail + +SAVED_PATH="$PATH" + +cleanup() { + PATH="${SAVED_PATH}" + echo "Cleanup" + ansible-playbook -i teardown.docker_machine.yml playbooks/teardown.yml + echo "Done" +} + +trap cleanup INT TERM EXIT + +echo "Pre-setup (install docker, docker-machine)" +ANSIBLE_ROLES_PATH=.. ansible-playbook playbooks/pre-setup.yml + +echo "Print docker-machine version" +docker-machine --version + +echo "Check preconditions" +# Host should NOT be known to Ansible before the test starts +ansible-inventory -i inventory_1.docker_machine.yml --host vm >/dev/null && exit 1 + +echo "Test that the docker_machine inventory plugin is being loaded" +ANSIBLE_DEBUG=yes ansible-inventory -i inventory_1.docker_machine.yml --list | grep -F "Loading InventoryModule 'docker_machine'" + +echo "Setup" +ansible-playbook playbooks/setup.yml + +echo "Test docker_machine inventory 1" +ansible-playbook -i inventory_1.docker_machine.yml playbooks/test_inventory_1.yml + +echo "Activate Docker Machine mock" +PATH=${SCRIPT_DIR}:$PATH + +echo "Test docker_machine inventory 2: daemon_env=require daemon env success=yes" +ansible-inventory -i inventory_2.docker_machine.yml --list + +echo "Test docker_machine inventory 2: daemon_env=require daemon env success=no" +export MOCK_ERROR_IN=env +ansible-inventory -i inventory_2.docker_machine.yml --list +unset MOCK_ERROR_IN + +echo "Test docker_machine inventory 3: daemon_env=optional daemon env success=yes" +ansible-inventory -i inventory_3.docker_machine.yml --list + +echo "Test docker_machine inventory 3: daemon_env=optional daemon env success=no" +export MOCK_ERROR_IN=env +ansible-inventory -i inventory_2.docker_machine.yml --list +unset MOCK_ERROR_IN + +echo "Deactivate Docker Machine mock" +PATH="${SAVED_PATH}" diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/teardown.docker_machine.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/teardown.docker_machine.yml new file mode 100644 index 000000000..d1ce95ce2 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_machine/teardown.docker_machine.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +plugin: community.docker.docker_machine +daemon_env: skip +running_required: false diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/aliases b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/aliases new file mode 100644 index 000000000..50e0e5f3a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/2 +destructive +needs/root diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/inventory_1.docker_swarm.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/inventory_1.docker_swarm.yml new file mode 100644 index 000000000..4371f6c14 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/inventory_1.docker_swarm.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +plugin: community.docker.docker_swarm +docker_host: unix://var/run/docker.sock diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/inventory_2.docker_swarm.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/inventory_2.docker_swarm.yml new file mode 100644 index 000000000..35fe21bf7 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/inventory_2.docker_swarm.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +plugin: community.docker.docker_swarm +docker_host: unix://var/run/docker.sock +verbose_output: false +include_host_uri: true diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/meta/main.yml new file mode 100644 index 000000000..5769ff1cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/swarm_cleanup.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/swarm_cleanup.yml new file mode 100644 index 000000000..4039a6bde --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/swarm_cleanup.yml @@ -0,0 +1,22 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local + gather_facts: true + tasks: + - name: Make sure swarm is removed + docker_swarm: + state: absent + force: true + + - name: remove docker pagkages + action: "{{ ansible_facts.pkg_mgr }}" + args: + name: + - docker + - docker-ce + - docker-ce-cli + state: absent diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/swarm_setup.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/swarm_setup.yml new file mode 100644 index 000000000..1ae4c63fe --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/swarm_setup.yml @@ -0,0 +1,19 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local + vars: + docker_skip_cleanup: true + + tasks: + - name: Setup docker + import_role: + name: setup_docker + + - name: Create a Swarm cluster + community.docker.docker_swarm: + state: present + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/test_inventory_1.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/test_inventory_1.yml new file mode 100644 index 000000000..77fcc3715 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/test_inventory_1.yml @@ -0,0 +1,62 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local # otherwise Ansible will complain that it cannot connect via ssh to 127.0.0.1:22 + gather_facts: false + tasks: + - name: Show all groups + debug: + var: groups + - name: Make sure docker_swarm groups are there + assert: + that: + - groups.all | length > 0 + - groups.leader | length == 1 + - groups.manager | length > 0 + - groups.worker | length >= 0 + - groups.nonleaders | length >= 0 + +- hosts: all + connection: local # otherwise Ansible will complain that it cannot connect via ssh to 127.0.0.1:22 + vars: + # for some reason, Ansible can't find the Python interpreter when connecting to the nodes, + # which is in fact just localhost in disguise. That's why we use ansible_playbook_python. + ansible_python_interpreter: "{{ ansible_playbook_python }}" + tasks: + - name: Check for groups + assert: + that: + - "groups.manager | length > 0" + - "groups.worker | length >= 0" + - "groups.leader | length == 1" + run_once: true + + - name: List manager group + debug: + var: groups.manager + run_once: true + + - name: List worker group + debug: + var: groups.worker + run_once: true + + - name: List leader group + debug: + var: groups.leader + run_once: true + + - name: Print ansible_host per host + debug: + var: ansible_host + + - name: Make sure docker_swarm_node_attributes is available + assert: + that: + - docker_swarm_node_attributes is not undefined + - name: Print docker_swarm_node_attributes per host + debug: + var: docker_swarm_node_attributes diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/test_inventory_2.yml b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/test_inventory_2.yml new file mode 100644 index 000000000..091b891a9 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/playbooks/test_inventory_2.yml @@ -0,0 +1,39 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: 127.0.0.1 + connection: local # otherwise Ansible will complain that it cannot connect via ssh to 127.0.0.1:22 + gather_facts: false + tasks: + - name: Show all groups + debug: + var: groups + - name: Make sure docker_swarm groups are there + assert: + that: + - groups.all | length > 0 + - groups.leader | length == 1 + - groups.manager | length > 0 + - groups.worker | length >= 0 + - groups.nonleaders | length >= 0 + +- hosts: all + connection: local # otherwise Ansible will complain that it cannot connect via ssh to 127.0.0.1:22 + vars: + # for some reason, Ansible can't find the Python interpreter when connecting to the nodes, + # which is in fact just localhost in disguise. That's why we use ansible_playbook_python. + ansible_python_interpreter: "{{ ansible_playbook_python }}" + tasks: + - name: Make sure docker_swarm_node_attributes is not available + assert: + that: + - docker_swarm_node_attributes is undefined + - name: Make sure ansible_host_uri is available + assert: + that: + - ansible_host_uri is defined + - name: Print ansible_host_uri + debug: + var: ansible_host_uri diff --git a/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/runme.sh b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/runme.sh new file mode 100755 index 000000000..746b8592f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/inventory_docker_swarm/runme.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -n "$DEBUG" || -n "$ANSIBLE_DEBUG" ]] && set -x + +set -euo pipefail + +cleanup() { + echo "Cleanup" + ansible-playbook playbooks/swarm_cleanup.yml + echo "Done" +} + +trap cleanup INT TERM EXIT + +echo "Setup" +ANSIBLE_ROLES_PATH=.. ansible-playbook playbooks/swarm_setup.yml + +echo "Test docker_swarm inventory 1" +ansible-playbook -i inventory_1.docker_swarm.yml playbooks/test_inventory_1.yml + +echo "Test docker_swarm inventory 2" +ansible-playbook -i inventory_2.docker_swarm.yml playbooks/test_inventory_2.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/aliases b/ansible_collections/community/docker/tests/integration/targets/setup_docker/aliases new file mode 100644 index 000000000..0a430dff1 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +needs/target/setup_epel diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/defaults/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/defaults/main.yml new file mode 100644 index 000000000..0509fe0ef --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/defaults/main.yml @@ -0,0 +1,23 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_cli_version: '0.0' +docker_api_version: '0.0' +docker_py_version: '0.0' +docker_skip_cleanup: true +docker_prereq_packages: [] +docker_packages: + - docker-ce +docker_cli_packages: + - docker-ce-cli + +docker_pip_extra_packages: [] +docker_pip_package: docker +docker_pip_package_limit: '' + +docker_cleanup_packages: + - docker + - docker-ce + - docker-ce-cli diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/handlers/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/handlers/main.yml new file mode 100644 index 000000000..96ca226c7 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/handlers/main.yml @@ -0,0 +1,19 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Remove pip packages + pip: + state: present + name: "{{ [docker_pip_package] | union(docker_pip_extra_packages) }}" + listen: cleanup docker + when: not docker_skip_cleanup | bool + +- name: Remove docker pagkages + action: "{{ ansible_facts.pkg_mgr }}" + args: + name: "{{ docker_cleanup_packages }}" + state: absent + listen: cleanup docker + when: not docker_skip_cleanup | bool diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/meta/main.yml new file mode 100644 index 000000000..d4a5c7d05 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_remote_constraints + - setup_pkg_mgr diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Alpine.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Alpine.yml new file mode 100644 index 000000000..64f6eb34a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Alpine.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker + apk: + name: docker + update_cache: true + notify: cleanup docker diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Archlinux.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Archlinux.yml new file mode 100644 index 000000000..3a67ff2b6 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Archlinux.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker + community.general.pacman: + name: docker + update_cache: true + notify: cleanup docker diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Debian.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Debian.yml new file mode 100644 index 000000000..0b5bdcb1a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Debian.yml @@ -0,0 +1,50 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Get OS version + shell: uname -r + register: os_version + +- name: Install pre-reqs + apt: + name: '{{ docker_prereq_packages }}' + state: present + update_cache: true + notify: cleanup docker + +- name: Add gpg key + shell: curl -fsSL https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg >key && apt-key add key + +- name: Add Docker repo + apt_repository: + repo: deb [arch=amd64] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable + state: present + +- block: + - name: Prevent service restart + copy: + content: exit 101 + dest: /usr/sbin/policy-rc.d + backup: true + mode: '0755' + register: policy_rc_d + + - name: Install Docker CE + apt: + name: '{{ docker_packages if needs_docker_daemon else docker_cli_packages }}' + state: present + + always: + - name: Restore /usr/sbin/policy-rc.d (if needed) + command: mv {{ policy_rc_d.backup_file }} /usr/sbin/policy-rc.d + when: + - '"backup_file" in policy_rc_d' + + - name: Remove /usr/sbin/policy-rc.d (if needed) + file: + path: /usr/sbin/policy-rc.d + state: absent + when: + - '"backup_file" not in policy_rc_d' diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Fedora.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Fedora.yml new file mode 100644 index 000000000..039751a7e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Fedora.yml @@ -0,0 +1,28 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Import GPG key + rpm_key: + key: https://download.docker.com/linux/fedora/gpg + state: present + +- name: Add repository + yum_repository: + file: docker-ce + name: docker-ce-stable + description: Docker CE Stable - $basearch + baseurl: https://download.docker.com/linux/fedora/{{ 31 if ansible_facts.distribution_major_version|int > 31 else '$releasever' }}/$basearch/stable + enabled: true + gpgcheck: true + +- name: Update cache + command: dnf makecache + +- name: Install docker + dnf: + name: "{{ docker_packages if needs_docker_daemon else docker_cli_packages }}" + state: present + enablerepo: docker-ce-test + notify: cleanup docker diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-7.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-7.yml new file mode 100644 index 000000000..87728ec6e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-7.yml @@ -0,0 +1,46 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# The RHEL extras repository must be enabled to provide the container-selinux package. +# See: https://docs.docker.com/engine/installation/linux/docker-ee/rhel/#install-using-the-repository + +- name: Install Docker pre-reqs + yum: + name: "{{ docker_prereq_packages }}" + state: present + notify: cleanup docker + +- name: Install epel repo which is missing on rhel-7 and is needed for pigz (needed for docker-ce 18) + include_role: + name: setup_epel + +- name: Enable extras repository for RHEL on AWS + # RHEL 7.6 uses REGION-rhel-server-extras and RHEL 7.7+ use rhel-7-server-rhui-extras-rpms + command: yum-config-manager --enable REGION-rhel-server-extras rhel-7-server-rhui-extras-rpms + +# Docker broke their .repo file, so we set it up ourselves +- name: Set-up repository + yum_repository: + name: docker-ce + description: docker-ce + baseurl: https://download.docker.com/linux/centos/{{ ansible_facts.distribution_major_version }}/$basearch/stable + gpgcheck: true + gpgkey: https://download.docker.com/linux/centos/gpg + +- name: Update cache + command: yum -y makecache fast + +- name: Install docker + yum: + name: "{{ docker_packages if needs_docker_daemon else docker_cli_packages }}" + state: present + notify: cleanup docker + +- name: Make sure the docker daemon is running (failure expected inside docker container) + service: + name: docker + state: started + ignore_errors: "{{ ansible_virtualization_type in ['docker', 'container', 'containerd'] }}" + when: needs_docker_daemon diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-8.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-8.yml new file mode 100644 index 000000000..1e259d97d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-8.yml @@ -0,0 +1,39 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# The RHEL extras repository must be enabled to provide the container-selinux package. +# See: https://docs.docker.com/engine/installation/linux/docker-ee/rhel/#install-using-the-repository + +- name: Install Docker pre-reqs + dnf: + name: "{{ docker_prereq_packages }}" + state: present + notify: cleanup docker + register: result + until: result is success + retries: 10 + delay: 2 + +# Docker broke their .repo file, so we set it up ourselves +- name: Set-up repository + yum_repository: + name: docker-ce + description: docker-ce + baseurl: https://download.docker.com/linux/centos/{{ ansible_facts.distribution_major_version }}/$basearch/stable + gpgcheck: true + gpgkey: https://download.docker.com/linux/centos/gpg + +- name: Install docker + dnf: + name: "{{ docker_packages if needs_docker_daemon else docker_cli_packages }}" + state: present + notify: cleanup docker + +- name: Make sure the docker daemon is running (failure expected inside docker container) + service: + name: docker + state: started + ignore_errors: "{{ ansible_virtualization_type in ['docker', 'container', 'containerd'] }}" + when: needs_docker_daemon diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-9.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-9.yml new file mode 100644 index 000000000..1e259d97d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/RedHat-9.yml @@ -0,0 +1,39 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# The RHEL extras repository must be enabled to provide the container-selinux package. +# See: https://docs.docker.com/engine/installation/linux/docker-ee/rhel/#install-using-the-repository + +- name: Install Docker pre-reqs + dnf: + name: "{{ docker_prereq_packages }}" + state: present + notify: cleanup docker + register: result + until: result is success + retries: 10 + delay: 2 + +# Docker broke their .repo file, so we set it up ourselves +- name: Set-up repository + yum_repository: + name: docker-ce + description: docker-ce + baseurl: https://download.docker.com/linux/centos/{{ ansible_facts.distribution_major_version }}/$basearch/stable + gpgcheck: true + gpgkey: https://download.docker.com/linux/centos/gpg + +- name: Install docker + dnf: + name: "{{ docker_packages if needs_docker_daemon else docker_cli_packages }}" + state: present + notify: cleanup docker + +- name: Make sure the docker daemon is running (failure expected inside docker container) + service: + name: docker + state: started + ignore_errors: "{{ ansible_virtualization_type in ['docker', 'container', 'containerd'] }}" + when: needs_docker_daemon diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Suse.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Suse.yml new file mode 100644 index 000000000..18974cb29 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/Suse.yml @@ -0,0 +1,12 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker 17 + community.general.zypper: + name: "{{ docker_packages if needs_docker_daemon else docker_cli_packages }}" + force: true + disable_gpg_check: true + update_cache: true + notify: cleanup docker diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/main.yml new file mode 100644 index 000000000..e8f078d3b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/tasks/main.yml @@ -0,0 +1,174 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Setup Docker + when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6'] + block: + - name: Detect whether we are running inside a container + current_container_facts: + + - name: Look for marker whether Docker was already set up + stat: + path: /root/community.docker-docker-is-set-up + register: docker_setup_marker + + - when: not docker_setup_marker.stat.exists + block: + - name: Determine whether Docker Daemon needs to be installed + set_fact: + needs_docker_daemon: '{{ not ansible_module_running_in_container }}' + + - name: Include distribution specific variables + include_vars: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.distribution }}.yml" + - "{{ ansible_facts.os_family }}.yml" + - default.yml + paths: + - "{{ role_path }}/vars" + + - name: Include distribution specific tasks + include_tasks: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.distribution }}.yml" + - "{{ ansible_facts.os_family }}.yml" + paths: + - "{{ role_path }}/tasks" + + - name: Make sure that docker is running + service: + name: docker + state: started + when: not ansible_module_running_in_container + + - name: Set marker that Docker was already set up + file: + path: /root/community.docker-docker-is-set-up + state: touch + when: docker_skip_cleanup + + # Detect docker API version + - name: Check Docker API version + command: "docker version -f {% raw %}'{{(index .Server.Components 0).Details.ApiVersion}}'{% endraw %}" + register: docker_api_version_stdout + ignore_errors: true + + - name: Limit docker pypi package version to < 4.3.0 + set_fact: + docker_pip_package_limit: '<4.3.0' + when: (docker_api_version_stdout.stdout | default('0.0')) is version('1.39', '<') + + - name: Install/upgrade Python requirements + pip: + name: "{{ [docker_pip_package ~ docker_pip_package_limit] + docker_pip_extra_packages }}" + extra_args: "-c {{ remote_constraints }}" + state: "{{ 'latest' if force_docker_sdk_for_python_pypi | default(false) else 'present' }}" + notify: cleanup docker + + # Detect docker CLI and docker-py versions + - name: Check Docker CLI version + command: "docker version -f {% raw %}'{{.Client.Version}}'{% endraw %}" + register: docker_cli_version_stdout + ignore_errors: true + + - name: Check docker-py version + command: "{{ ansible_python.executable }} -c 'import docker; print(docker.__version__)'" + register: docker_py_version_stdout + ignore_errors: true + + - set_fact: + docker_cli_version: "{{ docker_cli_version_stdout.stdout | default('0.0') }}" + docker_api_version: "{{ docker_api_version_stdout.stdout | default('0.0') }}" + docker_py_version: "{{ docker_py_version_stdout.stdout | default('0.0') }}" + + - debug: + msg: "Docker CLI version: {{ docker_cli_version }}; Docker API version: {{ docker_api_version }}; docker-py library version: {{ docker_py_version }}" + + - block: + # Cleanup docker daemon + - command: 'docker ps --no-trunc --format {% raw %}"{{.Names}}"{% endraw %}' + + - name: "Remove all ansible-docker-test-* docker containers" + shell: 'docker ps --no-trunc --format {% raw %}"{{.Names}}"{% endraw %} | grep "^ansible-docker-test-" | xargs -r docker rm -f' + register: docker_containers + retries: 3 + delay: 3 + until: docker_containers is success + ignore_errors: true + + - name: "Remove all ansible-docker-test-* docker volumes" + shell: 'docker volume ls --format {% raw %}"{{.Name}}"{% endraw %} | grep "^ansible-docker-test-" | xargs -r docker volume rm -f' + register: docker_volumes + ignore_errors: true + + - name: "Remove all ansible-docker-test-* docker networks" + shell: 'docker network ls --no-trunc --format {% raw %}"{{.Name}}"{% endraw %} | grep "^ansible-docker-test-" | xargs -r docker network rm' + register: docker_networks + ignore_errors: true + + - name: Cleaned docker resources + debug: + var: docker_resources + vars: + docker_resources: + containers: "{{ docker_containers.stdout_lines | default([]) }}" + volumes: "{{ docker_volumes.stdout_lines | default([]) }}" + networks: "{{ docker_networks.stdout_lines | default([]) }}" + + # List all existing docker resources + - name: List all docker containers + command: docker ps --no-trunc -a + register: docker_containers + ignore_errors: true + + - name: List all docker volumes + command: docker volume ls + register: docker_volumes + ignore_errors: true + + - name: List all docker networks + command: docker network ls --no-trunc + register: docker_networks + ignore_errors: true + + - name: List all docker images + command: docker images --no-trunc -a + register: docker_images + ignore_errors: true + + - name: Still existing docker resources + debug: + var: docker_resources + vars: + docker_resources: + containers: "{{ docker_containers.stdout_lines | default([]) }}" + volumes: "{{ docker_volumes.stdout_lines | default([]) }}" + networks: "{{ docker_networks.stdout_lines | default([]) }}" + images: "{{ docker_images.stdout_lines | default([]) }}" + + when: docker_cli_version is version('0.0', '>') + + - name: Inspect current container + docker_container_info: + name: "{{ ansible_module_container_id }}" + register: current_container_info + when: ansible_module_running_in_container + + - name: Determine network name + set_fact: + current_container_network_ip: "{{ (current_container_info.container.NetworkSettings.Networks | dictsort)[0].0 | default('') if ansible_module_running_in_container else '' }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Debian.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Debian.yml new file mode 100644 index 000000000..78f7555d5 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Debian.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_prereq_packages: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Fedora.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Fedora.yml new file mode 100644 index 000000000..f55df21f8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Fedora.yml @@ -0,0 +1,4 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-7.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-7.yml new file mode 100644 index 000000000..b1e28987c --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-7.yml @@ -0,0 +1,13 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_prereq_packages: + - yum-utils + - device-mapper-persistent-data + - lvm2 + - libseccomp + +docker_pip_extra_packages: + - requests==2.6.0 diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-8.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-8.yml new file mode 100644 index 000000000..7609400a0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-8.yml @@ -0,0 +1,17 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_prereq_packages: + - yum-utils + - device-mapper-persistent-data + - lvm2 + - libseccomp + - iptables + +docker_packages: + - docker-ce-19.03.13 + - docker-ce-cli-19.03.13 +docker_cli_packages: + - docker-ce-cli-19.03.13 diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-9.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-9.yml new file mode 100644 index 000000000..04fcae721 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/RedHat-9.yml @@ -0,0 +1,17 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_prereq_packages: + - yum-utils + - device-mapper-persistent-data + - lvm2 + - libseccomp + - iptables + +docker_packages: + - docker-ce # -19.03.13 + - docker-ce-cli # -19.03.13 +docker_cli_packages: + - docker-ce-cli # -19.03.13 diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Suse.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Suse.yml new file mode 100644 index 000000000..ab71ef5d7 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Suse.yml @@ -0,0 +1,12 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_packages: + - docker>=17 + +# OpenSUSE 15 does not seem to have docker-client (https://software.opensuse.org/package/docker-client) +# or any other Docker CLI-only package +docker_cli_packages: + - docker>=17 diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Ubuntu-14.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Ubuntu-14.yml new file mode 100644 index 000000000..d7c82455b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Ubuntu-14.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_pip_extra_packages: + # Installing requests >=2.12.0 on Ubuntu 14.04 breaks certificate validation. We restrict to an older version + # to ensure out get_url tests work out fine. This is only an issue if pyOpenSSL is also installed. + # Not sure why RHEL7 needs this specific version + - requests==2.6.0 diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Ubuntu-22.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Ubuntu-22.yml new file mode 100644 index 000000000..436eb2d64 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/Ubuntu-22.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_prereq_packages: + - ca-certificates + - curl diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/default.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/default.yml new file mode 100644 index 000000000..f55df21f8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/default.yml @@ -0,0 +1,4 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/main.env b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/main.env new file mode 100644 index 000000000..523271476 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/main.env @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Docker images for runme.sh based tests +DOCKER_TEST_IMAGE_PYTHON3=python:3-alpine diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/main.yml new file mode 100644 index 000000000..e4eafc24e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker/vars/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_test_image_digest_v1: e004c2cc521c95383aebb1fb5893719aa7a8eae2e7a71f316a4410784edb00a9 +docker_test_image_digest_v2: ee44b399df993016003bf5466bd3eeb221305e9d0fa831606bc7902d149c775b +docker_test_image_digest_base: quay.io/ansible/docker-test-containers +docker_test_image_hello_world: quay.io/ansible/docker-test-containers:hello-world +docker_test_image_hello_world_base: quay.io/ansible/docker-test-containers +docker_test_image_busybox: quay.io/ansible/docker-test-containers:busybox +docker_test_image_alpine: quay.io/ansible/docker-test-containers:alpine3.8 +docker_test_image_alpine_different: quay.io/ansible/docker-test-containers:alpine3.7 +docker_test_image_registry_nginx: quay.io/ansible/docker-test-containers:nginx-alpine +docker_test_image_registry: registry:2.6.1 diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/defaults/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/defaults/main.yml new file mode 100644 index 000000000..f701c90e3 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/defaults/main.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: false +docker_compose_packages: + - docker-compose +docker_compose_pip_packages: + - docker-compose diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/meta/main.yml new file mode 100644 index 000000000..b6e985d7f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_constraints diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Alpine.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Alpine.yml new file mode 100644 index 000000000..85042fdf0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Alpine.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + apk: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Archlinux.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Archlinux.yml new file mode 100644 index 000000000..2e62ff052 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Archlinux.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + pacman: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Debian.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Debian.yml new file mode 100644 index 000000000..1729ccabe --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Debian.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + apt: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Fedora.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Fedora.yml new file mode 100644 index 000000000..a5f3d467b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Fedora.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + dnf: + name: "{{ docker_compose_packages }}" + state: present + enablerepo: docker-ce-test diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-7.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-7.yml new file mode 100644 index 000000000..62f0e3738 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-7.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + yum: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-8.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-8.yml new file mode 100644 index 000000000..549868455 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-8.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + dnf: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-9.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-9.yml new file mode 100644 index 000000000..549868455 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/RedHat-9.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + dnf: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Suse.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Suse.yml new file mode 100644 index 000000000..46e50fd4d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/Suse.yml @@ -0,0 +1,12 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + community.general.zypper: + name: "{{ docker_compose_packages }}" + force: true + disable_gpg_check: true + update_cache: true + notify: cleanup docker diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/main.yml new file mode 100644 index 000000000..630885104 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/main.yml @@ -0,0 +1,16 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- set_fact: + has_docker_compose: false + +- when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6'] + include_tasks: + file: setup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/setup.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/setup.yml new file mode 100644 index 000000000..08c68a89a --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/tasks/setup.yml @@ -0,0 +1,59 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Include distribution specific variables + include_vars: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}-py{{ ansible_python.version.major }}.yml" + - "{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}-py{{ ansible_python.version.major }}.yml" + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.distribution }}-py{{ ansible_python.version.major }}.yml" + - "{{ ansible_facts.os_family }}-py{{ ansible_python.version.major }}.yml" + - "{{ ansible_facts.distribution }}.yml" + - "{{ ansible_facts.os_family }}.yml" + - default.yml + paths: + - "{{ role_path }}/vars" + +- block: + - name: Include distribution specific tasks + include_tasks: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}-py{{ ansible_python.version.major }}.yml" + - "{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}-py{{ ansible_python.version.major }}.yml" + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.distribution }}-py{{ ansible_python.version.major }}.yml" + - "{{ ansible_facts.os_family }}-py{{ ansible_python.version.major }}.yml" + - "{{ ansible_facts.distribution }}.yml" + - "{{ ansible_facts.os_family }}.yml" + paths: + - "{{ role_path }}/tasks" + + - name: Install docker-compose + pip: + state: present + name: "{{ docker_compose_pip_packages }}" + extra_args: "-c {{ remote_constraints }}" + + - name: Register docker-compose version + command: "{{ ansible_python.executable }} -c 'import compose; print(compose.__version__)'" + register: docker_compose_version + ignore_errors: true + + - name: Declare docker-compose version + set_fact: + docker_compose_version: "{{ docker_compose_version.stdout | default('0.0.0') }}" + + - name: Declare docker-compose as existing + set_fact: + has_docker_compose: true + + when: not skip_docker_compose diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/CentOS-8.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/CentOS-8.yml new file mode 100644 index 000000000..c5d18002b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/CentOS-8.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-7.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-7.yml new file mode 100644 index 000000000..c5d18002b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-7.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-8.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-8.yml new file mode 100644 index 000000000..7279eac17 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-8.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_compose_packages: [] diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-9.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-9.yml new file mode 100644 index 000000000..7279eac17 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/RedHat-9.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_compose_packages: [] diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Suse-py2.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Suse-py2.yml new file mode 100644 index 000000000..c5d18002b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Suse-py2.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Suse-py3.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Suse-py3.yml new file mode 100644 index 000000000..46c58b253 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Suse-py3.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_compose_pip_packages: [] diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu-16.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu-16.yml new file mode 100644 index 000000000..c5d18002b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu-16.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu-18.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu-18.yml new file mode 100644 index 000000000..c5d18002b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu-18.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu.yml new file mode 100644 index 000000000..46c58b253 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/Ubuntu.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_compose_pip_packages: [] diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/default.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/default.yml new file mode 100644 index 000000000..f55df21f8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose/vars/default.yml @@ -0,0 +1,4 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/defaults/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/defaults/main.yml new file mode 100644 index 000000000..4f84d3ac0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/defaults/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: false +docker_compose_packages: + - docker-compose-plugin diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/meta/main.yml new file mode 100644 index 000000000..b6e985d7f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_docker + - setup_remote_constraints diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Alpine.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Alpine.yml new file mode 100644 index 000000000..85042fdf0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Alpine.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + apk: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Archlinux.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Archlinux.yml new file mode 100644 index 000000000..2e62ff052 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Archlinux.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + pacman: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Debian.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Debian.yml new file mode 100644 index 000000000..1729ccabe --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Debian.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + apt: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Fedora.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Fedora.yml new file mode 100644 index 000000000..a5f3d467b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Fedora.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + dnf: + name: "{{ docker_compose_packages }}" + state: present + enablerepo: docker-ce-test diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-7.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-7.yml new file mode 100644 index 000000000..62f0e3738 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-7.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + yum: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-8.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-8.yml new file mode 100644 index 000000000..549868455 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-8.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + dnf: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-9.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-9.yml new file mode 100644 index 000000000..549868455 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/RedHat-9.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + dnf: + name: "{{ docker_compose_packages }}" + state: present diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Suse.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Suse.yml new file mode 100644 index 000000000..46e50fd4d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/Suse.yml @@ -0,0 +1,12 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install docker-compose as system package + community.general.zypper: + name: "{{ docker_compose_packages }}" + force: true + disable_gpg_check: true + update_cache: true + notify: cleanup docker diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/main.yml new file mode 100644 index 000000000..e379f0fb4 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/main.yml @@ -0,0 +1,16 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- set_fact: + has_docker_compose: false + +- when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6'] and ansible_python_version is version('3.7', '>=') + include_tasks: + file: setup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/setup.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/setup.yml new file mode 100644 index 000000000..3da96a339 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/tasks/setup.yml @@ -0,0 +1,50 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Include distribution specific variables + include_vars: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.distribution }}.yml" + - "{{ ansible_facts.os_family }}.yml" + - default.yml + paths: + - "{{ role_path }}/vars" + +- block: + - name: Include distribution specific tasks + include_tasks: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.distribution }}.yml" + - "{{ ansible_facts.os_family }}.yml" + paths: + - "{{ role_path }}/tasks" + + - name: Install Python on Whales + pip: + state: present + name: python-on-whales + extra_args: "-c {{ remote_constraints }}" + + - name: Register docker-compose version + command: "docker compose version --short" + register: docker_compose_version + + - name: Declare docker-compose version + set_fact: + docker_compose_version: "{{ docker_compose_version.stdout }}" + + - name: Declare docker-compose as existing + set_fact: + has_docker_compose: '{{ docker_compose_version is version("2.0", ">=") }}' + + when: not skip_docker_compose diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Alpine.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Alpine.yml new file mode 100644 index 000000000..c5d18002b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Alpine.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Archlinux.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Archlinux.yml new file mode 100644 index 000000000..99d81828b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Archlinux.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_compose_packages: + - docker-compose diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Fedora.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Fedora.yml new file mode 100644 index 000000000..c5d18002b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/Fedora.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +skip_docker_compose: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/default.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/default.yml new file mode 100644 index 000000000..f55df21f8 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_compose_v2/vars/default.yml @@ -0,0 +1,4 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/aliases b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/aliases new file mode 100644 index 000000000..357972ff5 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +needs/target/setup_docker +needs/target/setup_openssl diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/files/nginx.conf b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/files/nginx.conf new file mode 100644 index 000000000..c3b0d3342 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/files/nginx.conf @@ -0,0 +1,50 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +events { + worker_connections 16; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + error_log /dev/stdout info; + access_log /dev/stdout; + + server { + listen *:5000 ssl; + server_name test-registry.ansible.com; + server_name_in_redirect on; + + ssl_protocols TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256'; + ssl_ecdh_curve X25519:secp521r1:secp384r1; + ssl_prefer_server_ciphers on; + ssl_certificate /etc/nginx/cert.pem; + ssl_certificate_key /etc/nginx/cert.key; + + location / { + return 401; + } + + location /v2/ { + proxy_pass http://real-registry:5000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Request-Start $msec; + + client_max_body_size 0; + chunked_transfer_encoding on; + + auth_basic "Ansible Test Docker Registry"; + auth_basic_user_file /etc/nginx/nginx.htpasswd; + } + } +} diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/files/nginx.htpasswd b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/files/nginx.htpasswd new file mode 100644 index 000000000..e3ff626fb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/files/nginx.htpasswd @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +testuser:{PLAIN}hunter2 diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/handlers/cleanup.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/handlers/cleanup.yml new file mode 100644 index 000000000..0a1f363cb --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/handlers/cleanup.yml @@ -0,0 +1,59 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Make sure all images are removed" + docker_image: + name: "{{ item }}" + state: absent + with_items: "{{ docker_registry_setup_inames }}" + +- name: "Get registry logs" + command: "docker logs {{ docker_registry_container_name_registry }}" + register: registry_logs + no_log: true + ignore_errors: true + +- name: "Printing registry logs" + debug: + var: registry_logs.stdout_lines + when: registry_logs is not failed + +- name: "Get nginx logs for first instance" + command: "docker logs {{ docker_registry_container_name_nginx }}" + register: nginx_logs + no_log: true + ignore_errors: true + +- name: "Get nginx logs for second instance" + command: "docker logs {{ docker_registry_container_name_nginx2 }}" + register: nginx2_logs + no_log: true + ignore_errors: true + +- name: "Printing nginx logs for first instance" + debug: + var: nginx_logs.stdout_lines + when: nginx_logs is not failed + +- name: "Printing nginx logs for second instance" + debug: + var: nginx2_logs.stdout_lines + when: nginx_logs is not failed + +- name: "Make sure all containers are removed" + docker_container: + name: "{{ item }}" + state: absent + force_kill: true + with_items: "{{ docker_registry_setup_cnames }}" + register: result + retries: 3 + delay: 3 + until: result is success + +- name: "Make sure all volumes are removed" + command: "docker volume rm -f {{ item }}" + with_items: "{{ docker_registry_setup_vnames }}" + ignore_errors: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/handlers/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/handlers/main.yml new file mode 100644 index 000000000..f1bdaaced --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/handlers/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Remove test registry + include_tasks: ../handlers/cleanup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/meta/main.yml new file mode 100644 index 000000000..4ab14ed10 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/meta/main.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + #- setup_docker -- done in setup.yml, to work around cleanup problems! + - setup_openssl + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/main.yml new file mode 100644 index 000000000..55c65477d --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/main.yml @@ -0,0 +1,13 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6'] + include_tasks: + file: setup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/setup-frontend.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/setup-frontend.yml new file mode 100644 index 000000000..25bb2029e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/setup-frontend.yml @@ -0,0 +1,120 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Set up first nginx frontend for registry +- name: Start nginx frontend for registry + docker_volume: + name: '{{ docker_registry_container_name_frontend }}' + state: present + +- name: Create container for nginx frontend for registry + docker_container: + state: stopped + name: '{{ docker_registry_container_name_frontend }}' + image: "{{ docker_test_image_registry_nginx }}" + ports: 5000 + # `links` does not work when using a network. That's why the docker_container task + # in setup.yml specifies `aliases` so we get the same effect. + links: + - '{{ docker_registry_container_name_registry }}:real-registry' + volumes: + - '{{ docker_registry_container_name_frontend }}:/etc/nginx/' + network_mode: '{{ current_container_network_ip | default(omit, true) }}' + networks: >- + {{ + [dict([['name', current_container_network_ip]])] + if current_container_network_ip not in ['', 'bridge'] else omit + }} + register: nginx_container + +- name: Copy config files + copy: + src: "{{ item }}" + dest: "{{ remote_tmp_dir }}/{{ item }}" + mode: "0644" + loop: + - nginx.conf + - nginx.htpasswd + +- name: Copy static files into volume + docker_container_copy_into: + container: '{{ docker_registry_container_name_frontend }}' + path: '{{ remote_tmp_dir }}/{{ item }}' + container_path: '/etc/nginx/{{ item }}' + owner_id: 0 + group_id: 0 + loop: + - nginx.conf + - nginx.htpasswd + register: can_copy_files + ignore_errors: true + +- when: can_copy_files is not failed + block: + + - name: Create private key for frontend certificate + community.crypto.openssl_privatekey: + path: '{{ remote_tmp_dir }}/cert.key' + type: ECC + curve: secp256r1 + force: true + + - name: Create CSR for frontend certificate + community.crypto.openssl_csr: + path: '{{ remote_tmp_dir }}/cert.csr' + privatekey_path: '{{ remote_tmp_dir }}/cert.key' + subject_alt_name: + - DNS:test-registry.ansible.com + + - name: Create frontend certificate + community.crypto.x509_certificate: + path: '{{ remote_tmp_dir }}/cert.pem' + csr_path: '{{ remote_tmp_dir }}/cert.csr' + privatekey_path: '{{ remote_tmp_dir }}/cert.key' + provider: selfsigned + + - name: Copy dynamic files into volume + docker_container_copy_into: + container: '{{ docker_registry_container_name_frontend }}' + path: '{{ remote_tmp_dir }}/{{ item }}' + container_path: '/etc/nginx/{{ item }}' + owner_id: 0 + group_id: 0 + loop: + - cert.pem + - cert.key + + - name: Start nginx frontend for registry + docker_container: + name: '{{ docker_registry_container_name_frontend }}' + state: started + register: nginx_container + + - name: Output nginx container network settings + debug: + var: nginx_container.container.NetworkSettings + + - name: Get registry URL + set_fact: + # Note that this host/port combination is used by the Docker daemon, that's why `localhost` is appropriate! + # This host/port combination cannot be used if the tests are running inside a docker container. + docker_registry_frontend_address: localhost:{{ nginx_container.container.NetworkSettings.Ports['5000/tcp'].0.HostPort }} + # The following host/port combination can be used from inside the docker container. + docker_registry_frontend_address_internal: "{{ nginx_container.container.NetworkSettings.Networks[current_container_network_ip].IPAddress if current_container_network_ip else nginx_container.container.NetworkSettings.IPAddress }}:5000" + + - name: Wait for registry frontend + uri: + url: https://{{ docker_registry_frontend_address_internal }}/v2/ + url_username: testuser + url_password: hunter2 + validate_certs: false + register: result + until: result is success + retries: 5 + delay: 1 + +- set_fact: + docker_registry_frontend_address: 'n/a' + when: can_copy_files is failed diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/setup.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/setup.yml new file mode 100644 index 000000000..b3a8662ee --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/tasks/setup.yml @@ -0,0 +1,84 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Register registry cleanup + # This must be done **before** docker is set up (see next task), to ensure that the + # registry is removed **before** docker itself is removed. This is necessary as the + # registry and its frontends run as docker containers. + command: 'true' + notify: Remove test registry + +- name: Setup Docker + # Please note that we do setup_docker here and not via meta/main.yml to avoid the problem that + # our cleanup is called **after** setup_docker's cleanup has been called! + include_role: + name: setup_docker + +- name: Create random name prefix and test registry name + set_fact: + docker_registry_container_name_registry: '{{ ''ansible-docker-test-registry-%0x'' % ((2**32) | random) }}' + docker_registry_container_name_nginx: '{{ ''ansible-docker-test-registry-frontend-%0x'' % ((2**32) | random) }}' + docker_registry_container_name_nginx2: '{{ ''ansible-docker-test-registry-frontend2-%0x'' % ((2**32) | random) }}' + +- name: Create image and container list + set_fact: + docker_registry_setup_inames: [] + docker_registry_setup_cnames: + - '{{ docker_registry_container_name_registry }}' + - '{{ docker_registry_container_name_nginx }}' + - '{{ docker_registry_container_name_nginx2 }}' + docker_registry_setup_vnames: + - '{{ docker_registry_container_name_nginx }}' + - '{{ docker_registry_container_name_nginx2 }}' + +- debug: + msg: Using test registry name {{ docker_registry_container_name_registry }} and nginx frontend names {{ docker_registry_container_name_nginx }} and {{ docker_registry_container_name_nginx2 }} + +- fail: msg="Too old docker / docker-py version to set up docker registry!" + when: not(docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) + +- when: docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.25', '>=') + block: + + # Set up registry container + - name: Start test registry + docker_container: + name: '{{ docker_registry_container_name_registry }}' + image: "{{ docker_test_image_registry }}" + ports: 5000 + network_mode: '{{ current_container_network_ip | default(omit, true) }}' + # We need to define the alias `real-registry` here because the global `links` + # option for the NGINX containers (see setup-frontend.yml) does not work when + # using networks. + networks: >- + {{ + [dict([['name', current_container_network_ip], ['aliases', ['real-registry']]])] + if current_container_network_ip not in ['', 'bridge'] else omit + }} + register: registry_container + + - name: Get registry URL + set_fact: + registry_address: localhost:{{ registry_container.container.NetworkSettings.Ports['5000/tcp'].0.HostPort }} + + # Set up first nginx frontend for registry + - include_tasks: setup-frontend.yml + vars: + docker_registry_container_name_frontend: '{{ docker_registry_container_name_nginx }}' + + - set_fact: + registry_frontend_address: '{{ docker_registry_frontend_address }}' + + # Set up second nginx frontend for registry + - include_tasks: setup-frontend.yml + vars: + docker_registry_container_name_frontend: '{{ docker_registry_container_name_nginx2 }}' + + - set_fact: + registry_frontend2_address: '{{ docker_registry_frontend_address }}' + + # Print addresses for registry and frontends + - debug: + msg: "Registry available under {{ registry_address }}, NGINX frontends available under {{ registry_frontend_address }} and {{ registry_frontend2_address }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/vars/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/vars/main.yml new file mode 100644 index 000000000..e4eafc24e --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_docker_registry/vars/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +docker_test_image_digest_v1: e004c2cc521c95383aebb1fb5893719aa7a8eae2e7a71f316a4410784edb00a9 +docker_test_image_digest_v2: ee44b399df993016003bf5466bd3eeb221305e9d0fa831606bc7902d149c775b +docker_test_image_digest_base: quay.io/ansible/docker-test-containers +docker_test_image_hello_world: quay.io/ansible/docker-test-containers:hello-world +docker_test_image_hello_world_base: quay.io/ansible/docker-test-containers +docker_test_image_busybox: quay.io/ansible/docker-test-containers:busybox +docker_test_image_alpine: quay.io/ansible/docker-test-containers:alpine3.8 +docker_test_image_alpine_different: quay.io/ansible/docker-test-containers:alpine3.7 +docker_test_image_registry_nginx: quay.io/ansible/docker-test-containers:nginx-alpine +docker_test_image_registry: registry:2.6.1 diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_epel/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_epel/tasks/main.yml new file mode 100644 index 000000000..9aa12db82 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_epel/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Install EPEL + yum: + name: https://s3.amazonaws.com/ansible-ci-files/test/integration/targets/setup_epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm + disable_gpg_check: true + when: ansible_facts.distribution in ['RedHat', 'CentOS'] diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_openssl/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/meta/main.yml new file mode 100644 index 000000000..d4a5c7d05 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_remote_constraints + - setup_pkg_mgr diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_openssl/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/tasks/main.yml new file mode 100644 index 000000000..b84159b7b --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/tasks/main.yml @@ -0,0 +1,35 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Include OS-specific variables + include_vars: '{{ ansible_os_family }}.yml' + when: not ansible_os_family == "Darwin" + +- name: Install cryptography (Python 3) + become: true + package: + name: '{{ cryptography_package_name_python3 }}' + when: ansible_os_family != 'Darwin' and ansible_python_version is version('3.0', '>=') + +- name: Install cryptography (Python 2) + become: true + package: + name: '{{ cryptography_package_name }}' + when: ansible_os_family != 'Darwin' and ansible_python_version is version('3.0', '<') + +- name: Install cryptography (Darwin, and potentially upgrade for other OSes) + become: true + pip: + name: cryptography>=1.3.0 + extra_args: "-c {{ remote_constraints }}" + +- name: Register cryptography version + command: "{{ ansible_python.executable }} -c 'import cryptography; print(cryptography.__version__)'" + register: cryptography_version diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Alpine.yml b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Alpine.yml new file mode 100644 index 000000000..460074797 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Alpine.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +cryptography_package_name: py-cryptography +cryptography_package_name_python3: py3-cryptography diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Archlinux.yml b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Archlinux.yml new file mode 100644 index 000000000..9fa799eb2 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Archlinux.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +cryptography_package_name: python-cryptography +cryptography_package_name_python3: python-cryptography diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Debian.yml b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Debian.yml new file mode 100644 index 000000000..4a3dc629f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Debian.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +cryptography_package_name: python-cryptography +cryptography_package_name_python3: python3-cryptography diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/FreeBSD.yml b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/FreeBSD.yml new file mode 100644 index 000000000..0bf73ee86 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/FreeBSD.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +cryptography_package_name: py27-cryptography +cryptography_package_name_python3: py36-cryptography diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/RedHat.yml b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/RedHat.yml new file mode 100644 index 000000000..4a3dc629f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/RedHat.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +cryptography_package_name: python-cryptography +cryptography_package_name_python3: python3-cryptography diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Suse.yml b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Suse.yml new file mode 100644 index 000000000..4a3dc629f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_openssl/vars/Suse.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +cryptography_package_name: python-cryptography +cryptography_package_name_python3: python3-cryptography diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_paramiko/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_paramiko/meta/main.yml new file mode 100644 index 000000000..7172cbe2c --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_paramiko/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_remote_constraints + - setup_openssl # so cryptography is installed diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_paramiko/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_paramiko/tasks/main.yml new file mode 100644 index 000000000..fc29334cd --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_paramiko/tasks/main.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install paramiko + pip: + name: "paramiko{% if cryptography_version.stdout is version('2.5.0', '<') %}<2.5.0{% endif %}" + extra_args: "-c {{ remote_constraints }}" + become: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_pkg_mgr/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_pkg_mgr/tasks/main.yml new file mode 100644 index 000000000..bc74b251f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_pkg_mgr/tasks/main.yml @@ -0,0 +1,28 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- set_fact: + pkg_mgr: community.general.pkgng + ansible_pkg_mgr: community.general.pkgng + cacheable: true + when: ansible_os_family == "FreeBSD" + +- set_fact: + pkg_mgr: community.general.zypper + ansible_pkg_mgr: community.general.zypper + cacheable: true + when: ansible_os_family == "Suse" + +- shell: + cmd: | + sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Linux-*.repo + sed -i 's%#baseurl=http://mirror.centos.org/$contentdir/$releasever/%baseurl=https://vault.centos.org/8.4.2105/%g' /etc/yum.repos.d/CentOS-Linux-*.repo + ignore_errors: true # This fails for CentOS Stream 8 + when: ansible_distribution in 'CentOS' and ansible_distribution_major_version == '8' diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/aliases b/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/aliases new file mode 100644 index 000000000..27ce6b087 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +needs/file/tests/utils/constraints.txt diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/meta/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/meta/main.yml new file mode 100644 index 000000000..982de6eb0 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_remote_tmp_dir diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/tasks/main.yml new file mode 100644 index 000000000..7e913fc91 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_remote_constraints/tasks/main.yml @@ -0,0 +1,18 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: record constraints.txt path on remote host + set_fact: + remote_constraints: "{{ remote_tmp_dir }}/constraints.txt" + +- name: copy constraints.txt to remote host + copy: + src: "{{ role_path }}/../../../utils/constraints.txt" + dest: "{{ remote_constraints }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml new file mode 100644 index 000000000..f1c55b04f --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: delete temporary directory + include_tasks: default-cleanup.yml + +- name: delete temporary directory (windows) + include_tasks: windows-cleanup.yml diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml b/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml new file mode 100644 index 000000000..cc74b70af --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: delete temporary directory + file: + path: "{{ remote_tmp_dir }}" + state: absent + no_log: true diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml b/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml new file mode 100644 index 000000000..c9d871c69 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml @@ -0,0 +1,16 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: create temporary directory + tempfile: + state: directory + suffix: .test + register: remote_tmp_dir + notify: + - delete temporary directory + +- name: record temporary directory + set_fact: + remote_tmp_dir: "{{ remote_tmp_dir.path }}" diff --git a/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml b/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml new file mode 100644 index 000000000..babbdad05 --- /dev/null +++ b/ansible_collections/community/docker/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml @@ -0,0 +1,20 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: make sure we have the ansible_os_family and ansible_distribution_version facts + setup: + gather_subset: distribution + when: ansible_facts == {} + +- include_tasks: "{{ lookup('first_found', files)}}" + vars: + files: + - "{{ ansible_os_family | lower }}.yml" + - "default.yml" diff --git a/ansible_collections/community/docker/tests/sanity/extra/extra-docs.json b/ansible_collections/community/docker/tests/sanity/extra/extra-docs.json new file mode 100644 index 000000000..9a28d174f --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/extra-docs.json @@ -0,0 +1,13 @@ +{ + "include_symlinks": false, + "prefixes": [ + "docs/docsite/", + "plugins/", + "roles/" + ], + "output": "path-line-column-message", + "requirements": [ + "ansible-core", + "antsibull-docs" + ] +} diff --git a/ansible_collections/community/docker/tests/sanity/extra/extra-docs.json.license b/ansible_collections/community/docker/tests/sanity/extra/extra-docs.json.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/extra-docs.json.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/extra/extra-docs.py b/ansible_collections/community/docker/tests/sanity/extra/extra-docs.py new file mode 100755 index 000000000..c636beb08 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/extra-docs.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +"""Check extra collection docs with antsibull-docs.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import sys +import subprocess + + +def main(): + """Main entry point.""" + env = os.environ.copy() + suffix = ':{env}'.format(env=env["ANSIBLE_COLLECTIONS_PATH"]) if 'ANSIBLE_COLLECTIONS_PATH' in env else '' + env['ANSIBLE_COLLECTIONS_PATH'] = '{root}{suffix}'.format(root=os.path.dirname(os.path.dirname(os.path.dirname(os.getcwd()))), suffix=suffix) + p = subprocess.run( + ['antsibull-docs', 'lint-collection-docs', '--plugin-docs', '--disallow-semantic-markup', '--skip-rstcheck', '.'], + env=env, + check=False, + ) + if p.returncode not in (0, 3): + print('{0}:0:0: unexpected return code {1}'.format(sys.argv[0], p.returncode)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/docker/tests/sanity/extra/licenses.json b/ansible_collections/community/docker/tests/sanity/extra/licenses.json new file mode 100644 index 000000000..50e47ca88 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/licenses.json @@ -0,0 +1,4 @@ +{ + "include_symlinks": false, + "output": "path-message" +} diff --git a/ansible_collections/community/docker/tests/sanity/extra/licenses.json.license b/ansible_collections/community/docker/tests/sanity/extra/licenses.json.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/licenses.json.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/extra/licenses.py b/ansible_collections/community/docker/tests/sanity/extra/licenses.py new file mode 100755 index 000000000..80eb795ef --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/licenses.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# Copyright (c) 2022, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +"""Prevent files without a correct license identifier from being added to the source tree.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import glob +import sys + + +def format_license_list(licenses): + if not licenses: + return '(empty)' + return ', '.join(['"%s"' % license for license in licenses]) + + +def find_licenses(filename, relax=False): + spdx_license_identifiers = [] + other_license_identifiers = [] + has_copyright = False + try: + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + line = line.rstrip() + if 'Copyright ' in line: + has_copyright = True + if 'Copyright: ' in line: + print('%s: found copyright line with "Copyright:". Please remove the colon.' % (filename, )) + if 'SPDX-FileCopyrightText: ' in line: + has_copyright = True + idx = line.find('SPDX-License-Identifier: ') + if idx >= 0: + lic_id = line[idx + len('SPDX-License-Identifier: '):] + spdx_license_identifiers.extend(lic_id.split(' OR ')) + if 'GNU General Public License' in line: + if 'v3.0+' in line: + other_license_identifiers.append('GPL-3.0-or-later') + if 'version 3 or later' in line: + other_license_identifiers.append('GPL-3.0-or-later') + if 'Simplified BSD License' in line: + other_license_identifiers.append('BSD-2-Clause') + if 'Apache License 2.0' in line: + other_license_identifiers.append('Apache-2.0') + if 'PSF License' in line or 'Python-2.0' in line: + other_license_identifiers.append('PSF-2.0') + if 'MIT License' in line: + other_license_identifiers.append('MIT') + except Exception as exc: + print('%s: error while processing file: %s' % (filename, exc)) + if len(set(spdx_license_identifiers)) < len(spdx_license_identifiers): + print('%s: found identical SPDX-License-Identifier values' % (filename, )) + if other_license_identifiers and set(other_license_identifiers) != set(spdx_license_identifiers): + print('%s: SPDX-License-Identifier yielded the license list %s, while manual guessing yielded the license list %s' % ( + filename, format_license_list(spdx_license_identifiers), format_license_list(other_license_identifiers))) + if not has_copyright and not relax: + print('%s: found no copyright notice' % (filename, )) + return sorted(spdx_license_identifiers) + + +def main(): + """Main entry point.""" + paths = sys.argv[1:] or sys.stdin.read().splitlines() + + # The following paths are allowed to have no license identifier + no_comments_allowed = [ + 'changelogs/fragments/*.yml', + 'changelogs/fragments/*.yaml', + ] + + # These files are completely ignored + ignore_paths = [ + '.ansible-test-timeout.json', + '.reuse/dep5', + 'LICENSES/*.txt', + 'COPYING', + ] + + no_comments_allowed = [fn for pattern in no_comments_allowed for fn in glob.glob(pattern)] + ignore_paths = [fn for pattern in ignore_paths for fn in glob.glob(pattern)] + + valid_licenses = [license_file[len('LICENSES/'):-len('.txt')] for license_file in glob.glob('LICENSES/*.txt')] + + for path in paths: + if path.startswith('./'): + path = path[2:] + if path in ignore_paths or path.startswith('tests/output/'): + continue + if os.stat(path).st_size == 0: + continue + if not path.endswith('.license') and os.path.exists(path + '.license'): + path = path + '.license' + valid_licenses_for_path = valid_licenses + if path.startswith('plugins/') and not path.startswith(('plugins/modules/', 'plugins/module_utils/')): + valid_licenses_for_path = [license for license in valid_licenses if license == 'GPL-3.0-or-later'] + licenses = find_licenses(path, relax=path in no_comments_allowed) + if not licenses: + if path not in no_comments_allowed: + print('%s: must have at least one license' % (path, )) + else: + for license in licenses: + if license not in valid_licenses_for_path: + print('%s: found not allowed license "%s", must be one of %s' % ( + path, license, format_license_list(valid_licenses_for_path))) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/docker/tests/sanity/extra/licenses.py.license b/ansible_collections/community/docker/tests/sanity/extra/licenses.py.license new file mode 100644 index 000000000..6c4958feb --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/licenses.py.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: 2022, Felix Fontein <felix@fontein.de> diff --git a/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.json b/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.json new file mode 100644 index 000000000..c789a7fd3 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.json @@ -0,0 +1,7 @@ +{ + "include_symlinks": true, + "prefixes": [ + "plugins/" + ], + "output": "path-message" +} diff --git a/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.json.license b/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.json.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.json.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.py b/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.py new file mode 100755 index 000000000..51444ab75 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/extra/no-unwanted-files.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +"""Prevent unwanted files from being added to the source tree.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import sys + + +def main(): + """Main entry point.""" + paths = sys.argv[1:] or sys.stdin.read().splitlines() + + allowed_extensions = ( + '.cs', + '.ps1', + '.psm1', + '.py', + ) + + skip_paths = set([ + ]) + + skip_directories = ( + ) + + for path in paths: + if path in skip_paths: + continue + + if any(path.startswith(skip_directory) for skip_directory in skip_directories): + continue + + ext = os.path.splitext(path)[1] + + if ext not in allowed_extensions: + print('%s: extension must be one of: %s' % (path, ', '.join(allowed_extensions))) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.10.txt b/ansible_collections/community/docker/tests/sanity/ignore-2.10.txt new file mode 100644 index 000000000..1a9f48884 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.10.txt @@ -0,0 +1,11 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate +.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate +plugins/modules/current_container_facts.py validate-modules:return-syntax-error +plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax +plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax +plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax +plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.10.txt.license b/ansible_collections/community/docker/tests/sanity/ignore-2.10.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.10.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.11.txt b/ansible_collections/community/docker/tests/sanity/ignore-2.11.txt new file mode 100644 index 000000000..1a9f48884 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.11.txt @@ -0,0 +1,11 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate +.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate +plugins/modules/current_container_facts.py validate-modules:return-syntax-error +plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax +plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax +plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax +plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.11.txt.license b/ansible_collections/community/docker/tests/sanity/ignore-2.11.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.11.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.12.txt b/ansible_collections/community/docker/tests/sanity/ignore-2.12.txt new file mode 100644 index 000000000..3d71834db --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.12.txt @@ -0,0 +1,3 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +plugins/modules/current_container_facts.py validate-modules:return-syntax-error +plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.12.txt.license b/ansible_collections/community/docker/tests/sanity/ignore-2.12.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.12.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.13.txt b/ansible_collections/community/docker/tests/sanity/ignore-2.13.txt new file mode 100644 index 000000000..2a06013da --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.13.txt @@ -0,0 +1,2 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.13.txt.license b/ansible_collections/community/docker/tests/sanity/ignore-2.13.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.13.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.14.txt b/ansible_collections/community/docker/tests/sanity/ignore-2.14.txt new file mode 100644 index 000000000..2a06013da --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.14.txt @@ -0,0 +1,2 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.14.txt.license b/ansible_collections/community/docker/tests/sanity/ignore-2.14.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.14.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.15.txt b/ansible_collections/community/docker/tests/sanity/ignore-2.15.txt new file mode 100644 index 000000000..2a06013da --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.15.txt @@ -0,0 +1,2 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.15.txt.license b/ansible_collections/community/docker/tests/sanity/ignore-2.15.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.15.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.16.txt b/ansible_collections/community/docker/tests/sanity/ignore-2.16.txt new file mode 100644 index 000000000..2a06013da --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.16.txt @@ -0,0 +1,2 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.16.txt.license b/ansible_collections/community/docker/tests/sanity/ignore-2.16.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.16.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.9.txt b/ansible_collections/community/docker/tests/sanity/ignore-2.9.txt new file mode 100644 index 000000000..81b68cbd8 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.9.txt @@ -0,0 +1,10 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate +.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate +plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax +plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax +plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax +plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin diff --git a/ansible_collections/community/docker/tests/sanity/ignore-2.9.txt.license b/ansible_collections/community/docker/tests/sanity/ignore-2.9.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/docker/tests/sanity/ignore-2.9.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/docker/tests/unit/compat/__init__.py b/ansible_collections/community/docker/tests/unit/compat/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/compat/__init__.py diff --git a/ansible_collections/community/docker/tests/unit/compat/builtins.py b/ansible_collections/community/docker/tests/unit/compat/builtins.py new file mode 100644 index 000000000..d548601d4 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/compat/builtins.py @@ -0,0 +1,20 @@ +# Copyright (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# +# Compat for python2.7 +# + +# One unittest needs to import builtins via __import__() so we need to have +# the string that represents it +try: + import __builtin__ # noqa: F401, pylint: disable=unused-import +except ImportError: + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' diff --git a/ansible_collections/community/docker/tests/unit/compat/mock.py b/ansible_collections/community/docker/tests/unit/compat/mock.py new file mode 100644 index 000000000..6ef80a7cb --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/compat/mock.py @@ -0,0 +1,30 @@ +# Copyright (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python3.x's unittest.mock module +''' +import sys # noqa: F401, pylint: disable=unused-import + +# Python 2.7 + +# Note: Could use the pypi mock library on python3.x as well as python2.x. It +# is the same as the python3 stdlib mock library + +try: + # Allow wildcard import because we really do want to import all of mock's + # symbols into this compat shim + # pylint: disable=wildcard-import,unused-wildcard-import + from unittest.mock import * # noqa: F401, pylint: disable=unused-import +except ImportError: + # Python 2 + # pylint: disable=wildcard-import,unused-wildcard-import + try: + from mock import * # noqa: F401, pylint: disable=unused-import + except ImportError: + print('You need the mock library installed on python2.x to run tests') diff --git a/ansible_collections/community/docker/tests/unit/compat/unittest.py b/ansible_collections/community/docker/tests/unit/compat/unittest.py new file mode 100644 index 000000000..1872e5833 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/compat/unittest.py @@ -0,0 +1,25 @@ +# Copyright (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python2.7's unittest module +''' + +import sys + +# Allow wildcard import because we really do want to import all of +# unittests's symbols into this compat shim +# pylint: disable=wildcard-import,unused-wildcard-import +if sys.version_info < (2, 7): + try: + # Need unittest2 on python2.6 + from unittest2 import * + except ImportError: + print('You need unittest2 installed on python2.6.x to run tests') +else: + from unittest import * diff --git a/ansible_collections/community/docker/tests/unit/plugins/connection/test_docker.py b/ansible_collections/community/docker/tests/unit/plugins/connection/test_docker.py new file mode 100644 index 000000000..5ae6a8e12 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/connection/test_docker.py @@ -0,0 +1,57 @@ +# Copyright (c) 2020 Red Hat, Inc. +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from io import StringIO + +from ansible_collections.community.docker.tests.unit.compat import mock +from ansible_collections.community.docker.tests.unit.compat import unittest +from ansible.errors import AnsibleError +from ansible.playbook.play_context import PlayContext +from ansible.plugins.loader import connection_loader + + +class TestDockerConnectionClass(unittest.TestCase): + + def setUp(self): + self.play_context = PlayContext() + self.play_context.prompt = ( + '[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: ' + ) + self.in_stream = StringIO() + with mock.patch('ansible_collections.community.docker.plugins.connection.docker.get_bin_path', return_value='docker'): + self.dc = connection_loader.get('community.docker.docker', self.play_context, self.in_stream) + + def tearDown(self): + pass + + @mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._old_docker_version', + return_value=('false', 'garbage', '', 1)) + @mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._new_docker_version', + return_value=('docker version', '1.2.3', '', 0)) + def test_docker_connection_module_too_old(self, mock_new_docker_verison, mock_old_docker_version): + self.dc._version = None + self.dc.remote_user = 'foo' + self.assertRaisesRegexp(AnsibleError, '^docker connection type requires docker 1.3 or higher$', self.dc._get_actual_user) + + @mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._old_docker_version', + return_value=('false', 'garbage', '', 1)) + @mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._new_docker_version', + return_value=('docker version', '1.7.0', '', 0)) + def test_docker_connection_module(self, mock_new_docker_verison, mock_old_docker_version): + self.dc._version = None + version = self.dc.docker_version + + # old version and new version fail + @mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._old_docker_version', + return_value=('false', 'garbage', '', 1)) + @mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._new_docker_version', + return_value=('false', 'garbage', '', 1)) + def test_docker_connection_module_wrong_cmd(self, mock_new_docker_version, mock_old_docker_version): + self.dc._version = None + self.dc.remote_user = 'foo' + self.assertRaisesRegexp(AnsibleError, '^Docker version check (.*?) failed: ', self.dc._get_actual_user) diff --git a/ansible_collections/community/docker/tests/unit/plugins/inventory/test_docker_containers.py b/ansible_collections/community/docker/tests/unit/plugins/inventory/test_docker_containers.py new file mode 100644 index 000000000..ea16c0d9f --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/inventory/test_docker_containers.py @@ -0,0 +1,214 @@ +# Copyright (c), Felix Fontein <felix@fontein.de>, 2020 +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +import pytest + +from ansible.inventory.data import InventoryData + +from ansible_collections.community.docker.plugins.inventory.docker_containers import InventoryModule + + +@pytest.fixture(scope="module") +def inventory(): + r = InventoryModule() + r.inventory = InventoryData() + return r + + +LOVING_THARP = { + 'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a', + 'Name': '/loving_tharp', + 'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385', + 'State': { + 'Running': True, + }, + 'Config': { + 'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0', + }, +} + + +LOVING_THARP_STACK = { + 'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a', + 'Name': '/loving_tharp', + 'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385', + 'State': { + 'Running': True, + }, + 'Config': { + 'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0', + 'Labels': { + 'com.docker.stack.namespace': 'my_stack', + }, + }, + 'NetworkSettings': { + 'Ports': { + '22/tcp': [ + { + 'HostIp': '0.0.0.0', + 'HostPort': '32802' + } + ], + }, + }, +} + + +LOVING_THARP_SERVICE = { + 'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a', + 'Name': '/loving_tharp', + 'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385', + 'State': { + 'Running': True, + }, + 'Config': { + 'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0', + 'Labels': { + 'com.docker.swarm.service.name': 'my_service', + }, + }, +} + + +def create_get_option(options, default=False): + def get_option(option): + if option in options: + return options[option] + return default + + return get_option + + +class FakeClient(object): + def __init__(self, *hosts): + self.get_results = {} + list_reply = [] + for host in hosts: + list_reply.append({ + 'Id': host['Id'], + 'Names': [host['Name']] if host['Name'] else [], + 'Image': host['Config']['Image'], + 'ImageId': host['Image'], + }) + self.get_results['/containers/{0}/json'.format(host['Name'])] = host + self.get_results['/containers/{0}/json'.format(host['Id'])] = host + self.get_results['/containers/json'] = list_reply + + def get_json(self, url, *param, **kwargs): + url = url.format(*param) + return self.get_results[url] + + +def test_populate(inventory, mocker): + client = FakeClient(LOVING_THARP) + + inventory.get_option = mocker.MagicMock(side_effect=create_get_option({ + 'verbose_output': True, + 'connection_type': 'docker-api', + 'add_legacy_groups': False, + 'compose': {}, + 'groups': {}, + 'keyed_groups': {}, + })) + inventory._populate(client) + + host_1 = inventory.inventory.get_host('loving_tharp') + host_1_vars = host_1.get_vars() + + assert host_1_vars['ansible_host'] == 'loving_tharp' + assert host_1_vars['ansible_connection'] == 'community.docker.docker_api' + assert 'ansible_ssh_host' not in host_1_vars + assert 'ansible_ssh_port' not in host_1_vars + assert 'docker_state' in host_1_vars + assert 'docker_config' in host_1_vars + assert 'docker_image' in host_1_vars + + assert len(inventory.inventory.groups['ungrouped'].hosts) == 0 + assert len(inventory.inventory.groups['all'].hosts) == 0 + assert len(inventory.inventory.groups) == 2 + assert len(inventory.inventory.hosts) == 1 + + +def test_populate_service(inventory, mocker): + client = FakeClient(LOVING_THARP_SERVICE) + + inventory.get_option = mocker.MagicMock(side_effect=create_get_option({ + 'verbose_output': False, + 'connection_type': 'docker-cli', + 'add_legacy_groups': True, + 'compose': {}, + 'groups': {}, + 'keyed_groups': {}, + 'docker_host': 'unix://var/run/docker.sock', + })) + inventory._populate(client) + + host_1 = inventory.inventory.get_host('loving_tharp') + host_1_vars = host_1.get_vars() + + assert host_1_vars['ansible_host'] == 'loving_tharp' + assert host_1_vars['ansible_connection'] == 'community.docker.docker' + assert 'ansible_ssh_host' not in host_1_vars + assert 'ansible_ssh_port' not in host_1_vars + assert 'docker_state' not in host_1_vars + assert 'docker_config' not in host_1_vars + assert 'docker_image' not in host_1_vars + + assert len(inventory.inventory.groups['ungrouped'].hosts) == 0 + assert len(inventory.inventory.groups['all'].hosts) == 0 + assert len(inventory.inventory.groups['7bd547963679e'].hosts) == 1 + assert len(inventory.inventory.groups['7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a'].hosts) == 1 + assert len(inventory.inventory.groups['image_quay.io/ansible/ubuntu1804-test-container:1.21.0'].hosts) == 1 + assert len(inventory.inventory.groups['loving_tharp'].hosts) == 1 + assert len(inventory.inventory.groups['running'].hosts) == 1 + assert len(inventory.inventory.groups['stopped'].hosts) == 0 + assert len(inventory.inventory.groups['service_my_service'].hosts) == 1 + assert len(inventory.inventory.groups['unix://var/run/docker.sock'].hosts) == 1 + assert len(inventory.inventory.groups) == 10 + assert len(inventory.inventory.hosts) == 1 + + +def test_populate_stack(inventory, mocker): + client = FakeClient(LOVING_THARP_STACK) + + inventory.get_option = mocker.MagicMock(side_effect=create_get_option({ + 'verbose_output': False, + 'connection_type': 'ssh', + 'add_legacy_groups': True, + 'compose': {}, + 'groups': {}, + 'keyed_groups': {}, + 'docker_host': 'unix://var/run/docker.sock', + 'default_ip': '127.0.0.1', + 'private_ssh_port': 22, + })) + inventory._populate(client) + + host_1 = inventory.inventory.get_host('loving_tharp') + host_1_vars = host_1.get_vars() + + assert host_1_vars['ansible_ssh_host'] == '127.0.0.1' + assert host_1_vars['ansible_ssh_port'] == '32802' + assert 'ansible_host' not in host_1_vars + assert 'ansible_connection' not in host_1_vars + assert 'docker_state' not in host_1_vars + assert 'docker_config' not in host_1_vars + assert 'docker_image' not in host_1_vars + + assert len(inventory.inventory.groups['ungrouped'].hosts) == 0 + assert len(inventory.inventory.groups['all'].hosts) == 0 + assert len(inventory.inventory.groups['7bd547963679e'].hosts) == 1 + assert len(inventory.inventory.groups['7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a'].hosts) == 1 + assert len(inventory.inventory.groups['image_quay.io/ansible/ubuntu1804-test-container:1.21.0'].hosts) == 1 + assert len(inventory.inventory.groups['loving_tharp'].hosts) == 1 + assert len(inventory.inventory.groups['running'].hosts) == 1 + assert len(inventory.inventory.groups['stopped'].hosts) == 0 + assert len(inventory.inventory.groups['stack_my_stack'].hosts) == 1 + assert len(inventory.inventory.groups['unix://var/run/docker.sock'].hosts) == 1 + assert len(inventory.inventory.groups) == 10 + assert len(inventory.inventory.hosts) == 1 diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/api/test_client.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/api/test_client.py new file mode 100644 index 000000000..ea0035655 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/api/test_client.py @@ -0,0 +1,702 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import datetime +import io +import json +import os +import re +import shutil +import socket +import struct +import tempfile +import threading +import time +import unittest +import sys + +from ansible.module_utils import six + +import pytest +import requests + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api import constants, errors +from ansible_collections.community.docker.plugins.module_utils._api.api.client import APIClient +from ansible_collections.community.docker.plugins.module_utils._api.constants import DEFAULT_DOCKER_API_VERSION +from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import convert_filters +from requests.packages import urllib3 + +from .. import fake_api + +try: + from unittest import mock +except ImportError: + import mock + + +DEFAULT_TIMEOUT_SECONDS = constants.DEFAULT_TIMEOUT_SECONDS + + +def response(status_code=200, content='', headers=None, reason=None, elapsed=0, + request=None, raw=None): + res = requests.Response() + res.status_code = status_code + if not isinstance(content, six.binary_type): + content = json.dumps(content).encode('ascii') + res._content = content + res.headers = requests.structures.CaseInsensitiveDict(headers or {}) + res.reason = reason + res.elapsed = datetime.timedelta(elapsed) + res.request = request + res.raw = raw + return res + + +def fake_resolve_authconfig(authconfig, registry=None, *args, **kwargs): + return None + + +def fake_inspect_container(self, container, tty=False): + return fake_api.get_fake_inspect_container(tty=tty)[1] + + +def fake_resp(method, url, *args, **kwargs): + key = None + if url in fake_api.fake_responses: + key = url + elif (url, method) in fake_api.fake_responses: + key = (url, method) + if not key: + raise Exception('{method} {url}'.format(method=method, url=url)) + status_code, content = fake_api.fake_responses[key]() + return response(status_code=status_code, content=content) + + +fake_request = mock.Mock(side_effect=fake_resp) + + +def fake_get(self, url, *args, **kwargs): + return fake_request('GET', url, *args, **kwargs) + + +def fake_post(self, url, *args, **kwargs): + return fake_request('POST', url, *args, **kwargs) + + +def fake_put(self, url, *args, **kwargs): + return fake_request('PUT', url, *args, **kwargs) + + +def fake_delete(self, url, *args, **kwargs): + return fake_request('DELETE', url, *args, **kwargs) + + +def fake_read_from_socket(self, response, stream, tty=False, demux=False): + return six.binary_type() + + +url_base = '{prefix}/'.format(prefix=fake_api.prefix) +url_prefix = '{0}v{1}/'.format( + url_base, + constants.DEFAULT_DOCKER_API_VERSION) + + +class BaseAPIClientTest(unittest.TestCase): + def setUp(self): + self.patcher = mock.patch.multiple( + 'ansible_collections.community.docker.plugins.module_utils._api.api.client.APIClient', + get=fake_get, + post=fake_post, + put=fake_put, + delete=fake_delete, + _read_from_socket=fake_read_from_socket + ) + self.patcher.start() + self.client = APIClient(version=DEFAULT_DOCKER_API_VERSION) + + def tearDown(self): + self.client.close() + self.patcher.stop() + + def base_create_payload(self, img='busybox', cmd=None): + if not cmd: + cmd = ['true'] + return {"Tty": False, "Image": img, "Cmd": cmd, + "AttachStdin": False, + "AttachStderr": True, "AttachStdout": True, + "StdinOnce": False, + "OpenStdin": False, "NetworkDisabled": False, + } + + +class DockerApiTest(BaseAPIClientTest): + def test_ctor(self): + with pytest.raises(errors.DockerException) as excinfo: + APIClient(version=1.12) + + assert str( + excinfo.value + ) == 'Version parameter must be a string or None. Found float' + + def test_url_valid_resource(self): + url = self.client._url('/hello/{0}/world', 'somename') + assert url == '{0}{1}'.format(url_prefix, 'hello/somename/world') + + url = self.client._url( + '/hello/{0}/world/{1}', 'somename', 'someothername' + ) + assert url == '{0}{1}'.format( + url_prefix, 'hello/somename/world/someothername' + ) + + url = self.client._url('/hello/{0}/world', 'some?name') + assert url == '{0}{1}'.format(url_prefix, 'hello/some%3Fname/world') + + url = self.client._url("/images/{0}/push", "localhost:5000/image") + assert url == '{0}{1}'.format( + url_prefix, 'images/localhost:5000/image/push' + ) + + def test_url_invalid_resource(self): + with pytest.raises(ValueError): + self.client._url('/hello/{0}/world', ['sakuya', 'izayoi']) + + def test_url_no_resource(self): + url = self.client._url('/simple') + assert url == '{0}{1}'.format(url_prefix, 'simple') + + def test_url_unversioned_api(self): + url = self.client._url( + '/hello/{0}/world', 'somename', versioned_api=False + ) + assert url == '{0}{1}'.format(url_base, 'hello/somename/world') + + def test_version(self): + self.client.version() + + fake_request.assert_called_with( + 'GET', + url_prefix + 'version', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_version_no_api_version(self): + self.client.version(False) + + fake_request.assert_called_with( + 'GET', + url_base + 'version', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_retrieve_server_version(self): + client = APIClient(version="auto") + assert isinstance(client._version, six.string_types) + assert not (client._version == "auto") + client.close() + + def test_auto_retrieve_server_version(self): + version = self.client._retrieve_server_version() + assert isinstance(version, six.string_types) + + def test_info(self): + self.client.info() + + fake_request.assert_called_with( + 'GET', + url_prefix + 'info', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_search(self): + self.client.get_json('/images/search', params={'term': 'busybox'}) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/search', + params={'term': 'busybox'}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_login(self): + self.client.login('sakuya', 'izayoi') + args = fake_request.call_args + assert args[0][0] == 'POST' + assert args[0][1] == url_prefix + 'auth' + assert json.loads(args[1]['data']) == { + 'username': 'sakuya', 'password': 'izayoi' + } + assert args[1]['headers'] == {'Content-Type': 'application/json'} + assert self.client._auth_configs.auths['docker.io'] == { + 'email': None, + 'password': 'izayoi', + 'username': 'sakuya', + 'serveraddress': None, + } + + def test_events(self): + self.client.events() + + fake_request.assert_called_with( + 'GET', + url_prefix + 'events', + params={'since': None, 'until': None, 'filters': None}, + stream=True, + timeout=None + ) + + def test_events_with_since_until(self): + ts = 1356048000 + now = datetime.datetime.utcfromtimestamp(ts) + since = now - datetime.timedelta(seconds=10) + until = now + datetime.timedelta(seconds=10) + + self.client.events(since=since, until=until) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'events', + params={ + 'since': ts - 10, + 'until': ts + 10, + 'filters': None + }, + stream=True, + timeout=None + ) + + def test_events_with_filters(self): + filters = {'event': ['die', 'stop'], + 'container': fake_api.FAKE_CONTAINER_ID} + + self.client.events(filters=filters) + + expected_filters = convert_filters(filters) + fake_request.assert_called_with( + 'GET', + url_prefix + 'events', + params={ + 'since': None, + 'until': None, + 'filters': expected_filters + }, + stream=True, + timeout=None + ) + + def _socket_path_for_client_session(self, client): + socket_adapter = client.get_adapter('http+docker://') + return socket_adapter.socket_path + + def test_url_compatibility_unix(self): + c = APIClient( + base_url="unix://socket", + version=DEFAULT_DOCKER_API_VERSION) + + assert self._socket_path_for_client_session(c) == '/socket' + + def test_url_compatibility_unix_triple_slash(self): + c = APIClient( + base_url="unix:///socket", + version=DEFAULT_DOCKER_API_VERSION) + + assert self._socket_path_for_client_session(c) == '/socket' + + def test_url_compatibility_http_unix_triple_slash(self): + c = APIClient( + base_url="http+unix:///socket", + version=DEFAULT_DOCKER_API_VERSION) + + assert self._socket_path_for_client_session(c) == '/socket' + + def test_url_compatibility_http(self): + c = APIClient( + base_url="http://hostname:1234", + version=DEFAULT_DOCKER_API_VERSION) + + assert c.base_url == "http://hostname:1234" + + def test_url_compatibility_tcp(self): + c = APIClient( + base_url="tcp://hostname:1234", + version=DEFAULT_DOCKER_API_VERSION) + + assert c.base_url == "http://hostname:1234" + + def test_remove_link(self): + self.client.delete_call('/containers/{0}', '3cc2351ab11b', params={'v': False, 'link': True, 'force': False}) + + fake_request.assert_called_with( + 'DELETE', + url_prefix + 'containers/3cc2351ab11b', + params={'v': False, 'link': True, 'force': False}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_stream_helper_decoding(self): + status_code, content = fake_api.fake_responses[url_prefix + 'events']() + content_str = json.dumps(content) + if six.PY3: + content_str = content_str.encode('utf-8') + body = io.BytesIO(content_str) + + # mock a stream interface + raw_resp = urllib3.HTTPResponse(body=body) + setattr(raw_resp._fp, 'chunked', True) + setattr(raw_resp._fp, 'chunk_left', len(body.getvalue()) - 1) + + # pass `decode=False` to the helper + raw_resp._fp.seek(0) + resp = response(status_code=status_code, content=content, raw=raw_resp) + result = next(self.client._stream_helper(resp)) + assert result == content_str + + # pass `decode=True` to the helper + raw_resp._fp.seek(0) + resp = response(status_code=status_code, content=content, raw=raw_resp) + result = next(self.client._stream_helper(resp, decode=True)) + assert result == content + + # non-chunked response, pass `decode=False` to the helper + setattr(raw_resp._fp, 'chunked', False) + raw_resp._fp.seek(0) + resp = response(status_code=status_code, content=content, raw=raw_resp) + result = next(self.client._stream_helper(resp)) + assert result == content_str.decode('utf-8') + + # non-chunked response, pass `decode=True` to the helper + raw_resp._fp.seek(0) + resp = response(status_code=status_code, content=content, raw=raw_resp) + result = next(self.client._stream_helper(resp, decode=True)) + assert result == content + + +class UnixSocketStreamTest(unittest.TestCase): + def setUp(self): + socket_dir = tempfile.mkdtemp() + self.build_context = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, socket_dir) + self.addCleanup(shutil.rmtree, self.build_context) + self.socket_file = os.path.join(socket_dir, 'test_sock.sock') + self.server_socket = self._setup_socket() + self.stop_server = False + server_thread = threading.Thread(target=self.run_server) + server_thread.daemon = True + server_thread.start() + self.response = None + self.request_handler = None + self.addCleanup(server_thread.join) + self.addCleanup(self.stop) + + def stop(self): + self.stop_server = True + + def _setup_socket(self): + server_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + server_sock.bind(self.socket_file) + # Non-blocking mode so that we can shut the test down easily + server_sock.setblocking(0) + server_sock.listen(5) + return server_sock + + def run_server(self): + try: + while not self.stop_server: + try: + connection, client_address = self.server_socket.accept() + except socket.error: + # Probably no connection to accept yet + time.sleep(0.01) + continue + + connection.setblocking(1) + try: + self.request_handler(connection) + finally: + connection.close() + finally: + self.server_socket.close() + + def early_response_sending_handler(self, connection): + data = b'' + headers = None + + connection.sendall(self.response) + while not headers: + data += connection.recv(2048) + parts = data.split(b'\r\n\r\n', 1) + if len(parts) == 2: + headers, data = parts + + mo = re.search(r'Content-Length: ([0-9]+)', headers.decode()) + assert mo + content_length = int(mo.group(1)) + + while True: + if len(data) >= content_length: + break + + data += connection.recv(2048) + + @pytest.mark.skipif( + constants.IS_WINDOWS_PLATFORM, reason='Unix only' + ) + def test_early_stream_response(self): + self.request_handler = self.early_response_sending_handler + lines = [] + for i in range(0, 50): + line = str(i).encode() + lines += [('%x' % len(line)).encode(), line] + lines.append(b'0') + lines.append(b'') + + self.response = ( + b'HTTP/1.1 200 OK\r\n' + b'Transfer-Encoding: chunked\r\n' + b'\r\n' + ) + b'\r\n'.join(lines) + + with APIClient( + base_url="http+unix://" + self.socket_file, + version=DEFAULT_DOCKER_API_VERSION) as client: + for i in range(5): + try: + params = { + 't': None, + 'remote': None, + 'q': False, + 'nocache': False, + 'rm': False, + 'forcerm': False, + 'pull': False, + 'dockerfile': 'Dockerfile', + } + headers = {'Content-Type': 'application/tar'} + data = b'...' + response = client._post(client._url('/build'), params=params, headers=headers, data=data, stream=True) + stream = client._stream_helper(response, decode=False) + break + except requests.ConnectionError as e: + if i == 4: + raise e + + assert list(stream) == [ + str(i).encode() for i in range(50) + ] + + +@pytest.mark.skip( + 'This test requires starting a networking server and tries to access it. ' + 'This does not work with network separation with Docker-based unit tests, ' + 'but it does work with podman-based unit tests.' +) +class TCPSocketStreamTest(unittest.TestCase): + stdout_data = b''' + Now, those children out there, they're jumping through the + flames in the hope that the god of the fire will make them fruitful. + Really, you can't blame them. After all, what girl would not prefer the + child of a god to that of some acne-scarred artisan? + ''' + stderr_data = b''' + And what of the true God? To whose glory churches and monasteries have been + built on these islands for generations past? Now shall what of Him? + ''' + + @classmethod + def setup_class(cls): + cls.server = six.moves.socketserver.ThreadingTCPServer( + ('', 0), cls.get_handler_class()) + cls.thread = threading.Thread(target=cls.server.serve_forever) + cls.thread.daemon = True + cls.thread.start() + cls.address = 'http://{0}:{1}'.format( + socket.gethostname(), cls.server.server_address[1]) + + @classmethod + def teardown_class(cls): + cls.server.shutdown() + cls.server.server_close() + cls.thread.join() + + @classmethod + def get_handler_class(cls): + stdout_data = cls.stdout_data + stderr_data = cls.stderr_data + + class Handler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler, object): + def do_POST(self): + resp_data = self.get_resp_data() + self.send_response(101) + self.send_header( + 'Content-Type', 'application/vnd.docker.raw-stream') + self.send_header('Connection', 'Upgrade') + self.send_header('Upgrade', 'tcp') + self.end_headers() + self.wfile.flush() + time.sleep(0.2) + self.wfile.write(resp_data) + self.wfile.flush() + + def get_resp_data(self): + path = self.path.split('/')[-1] + if path == 'tty': + return stdout_data + stderr_data + elif path == 'no-tty': + data = b'' + data += self.frame_header(1, stdout_data) + data += stdout_data + data += self.frame_header(2, stderr_data) + data += stderr_data + return data + else: + raise Exception('Unknown path {path}'.format(path=path)) + + @staticmethod + def frame_header(stream, data): + return struct.pack('>BxxxL', stream, len(data)) + + return Handler + + def request(self, stream=None, tty=None, demux=None): + assert stream is not None and tty is not None and demux is not None + with APIClient( + base_url=self.address, + version=DEFAULT_DOCKER_API_VERSION, + ) as client: + if tty: + url = client._url('/tty') + else: + url = client._url('/no-tty') + resp = client._post(url, stream=True) + return client._read_from_socket( + resp, stream=stream, tty=tty, demux=demux) + + def test_read_from_socket_tty(self): + res = self.request(stream=True, tty=True, demux=False) + assert next(res) == self.stdout_data + self.stderr_data + with self.assertRaises(StopIteration): + next(res) + + def test_read_from_socket_tty_demux(self): + res = self.request(stream=True, tty=True, demux=True) + assert next(res) == (self.stdout_data + self.stderr_data, None) + with self.assertRaises(StopIteration): + next(res) + + def test_read_from_socket_no_tty(self): + res = self.request(stream=True, tty=False, demux=False) + assert next(res) == self.stdout_data + assert next(res) == self.stderr_data + with self.assertRaises(StopIteration): + next(res) + + def test_read_from_socket_no_tty_demux(self): + res = self.request(stream=True, tty=False, demux=True) + assert (self.stdout_data, None) == next(res) + assert (None, self.stderr_data) == next(res) + with self.assertRaises(StopIteration): + next(res) + + def test_read_from_socket_no_stream_tty(self): + res = self.request(stream=False, tty=True, demux=False) + assert res == self.stdout_data + self.stderr_data + + def test_read_from_socket_no_stream_tty_demux(self): + res = self.request(stream=False, tty=True, demux=True) + assert res == (self.stdout_data + self.stderr_data, None) + + def test_read_from_socket_no_stream_no_tty(self): + res = self.request(stream=False, tty=False, demux=False) + res == self.stdout_data + self.stderr_data + + def test_read_from_socket_no_stream_no_tty_demux(self): + res = self.request(stream=False, tty=False, demux=True) + assert res == (self.stdout_data, self.stderr_data) + + +class UserAgentTest(unittest.TestCase): + def setUp(self): + self.patcher = mock.patch.object( + APIClient, + 'send', + return_value=fake_resp("GET", "%s/version" % fake_api.prefix) + ) + self.mock_send = self.patcher.start() + + def tearDown(self): + self.patcher.stop() + + def test_default_user_agent(self): + client = APIClient(version=DEFAULT_DOCKER_API_VERSION) + client.version() + + assert self.mock_send.call_count == 1 + headers = self.mock_send.call_args[0][0].headers + expected = 'ansible-community.docker' + assert headers['User-Agent'] == expected + + def test_custom_user_agent(self): + client = APIClient( + user_agent='foo/bar', + version=DEFAULT_DOCKER_API_VERSION) + client.version() + + assert self.mock_send.call_count == 1 + headers = self.mock_send.call_args[0][0].headers + assert headers['User-Agent'] == 'foo/bar' + + +class DisableSocketTest(unittest.TestCase): + class DummySocket: + def __init__(self, timeout=60): + self.timeout = timeout + + def settimeout(self, timeout): + self.timeout = timeout + + def gettimeout(self): + return self.timeout + + def setUp(self): + self.client = APIClient(version=DEFAULT_DOCKER_API_VERSION) + + def test_disable_socket_timeout(self): + """Test that the timeout is disabled on a generic socket object.""" + socket = self.DummySocket() + + self.client._disable_socket_timeout(socket) + + assert socket.timeout is None + + def test_disable_socket_timeout2(self): + """Test that the timeouts are disabled on a generic socket object + and it's _sock object if present.""" + socket = self.DummySocket() + socket._sock = self.DummySocket() + + self.client._disable_socket_timeout(socket) + + assert socket.timeout is None + assert socket._sock.timeout is None + + def test_disable_socket_timout_non_blocking(self): + """Test that a non-blocking socket does not get set to blocking.""" + socket = self.DummySocket() + socket._sock = self.DummySocket(0.0) + + self.client._disable_socket_timeout(socket) + + assert socket.timeout is None + assert socket._sock.timeout == 0.0 diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/fake_api.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/fake_api.py new file mode 100644 index 000000000..b794ff3d1 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/fake_api.py @@ -0,0 +1,668 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.docker.plugins.module_utils._api import constants + +from . import fake_stat + +CURRENT_VERSION = 'v{api_version}'.format(api_version=constants.DEFAULT_DOCKER_API_VERSION) + +FAKE_CONTAINER_ID = '3cc2351ab11b' +FAKE_IMAGE_ID = 'e9aa60c60128' +FAKE_EXEC_ID = 'd5d177f121dc' +FAKE_NETWORK_ID = '33fb6a3462b8' +FAKE_IMAGE_NAME = 'test_image' +FAKE_TARBALL_PATH = '/path/to/tarball' +FAKE_REPO_NAME = 'repo' +FAKE_TAG_NAME = 'tag' +FAKE_FILE_NAME = 'file' +FAKE_URL = 'myurl' +FAKE_PATH = '/path' +FAKE_VOLUME_NAME = 'perfectcherryblossom' +FAKE_NODE_ID = '24ifsmvkjbyhk' +FAKE_SECRET_ID = 'epdyrw4tsi03xy3deu8g8ly6o' +FAKE_SECRET_NAME = 'super_secret' + +# Each method is prefixed with HTTP method (get, post...) +# for clarity and readability + + +def get_fake_version(): + status_code = 200 + response = { + 'ApiVersion': '1.35', + 'Arch': 'amd64', + 'BuildTime': '2018-01-10T20:09:37.000000000+00:00', + 'Components': [{ + 'Details': { + 'ApiVersion': '1.35', + 'Arch': 'amd64', + 'BuildTime': '2018-01-10T20:09:37.000000000+00:00', + 'Experimental': 'false', + 'GitCommit': '03596f5', + 'GoVersion': 'go1.9.2', + 'KernelVersion': '4.4.0-112-generic', + 'MinAPIVersion': '1.12', + 'Os': 'linux' + }, + 'Name': 'Engine', + 'Version': '18.01.0-ce' + }], + 'GitCommit': '03596f5', + 'GoVersion': 'go1.9.2', + 'KernelVersion': '4.4.0-112-generic', + 'MinAPIVersion': '1.12', + 'Os': 'linux', + 'Platform': {'Name': ''}, + 'Version': '18.01.0-ce' + } + + return status_code, response + + +def get_fake_info(): + status_code = 200 + response = {'Containers': 1, 'Images': 1, 'Debug': False, + 'MemoryLimit': False, 'SwapLimit': False, + 'IPv4Forwarding': True} + return status_code, response + + +def post_fake_auth(): + status_code = 200 + response = {'Status': 'Login Succeeded', + 'IdentityToken': '9cbaf023786cd7'} + return status_code, response + + +def get_fake_ping(): + return 200, "OK" + + +def get_fake_search(): + status_code = 200 + response = [{'Name': 'busybox', 'Description': 'Fake Description'}] + return status_code, response + + +def get_fake_images(): + status_code = 200 + response = [{ + 'Id': FAKE_IMAGE_ID, + 'Created': '2 days ago', + 'Repository': 'busybox', + 'RepoTags': ['busybox:latest', 'busybox:1.0'], + }] + return status_code, response + + +def get_fake_image_history(): + status_code = 200 + response = [ + { + "Id": "b750fe79269d", + "Created": 1364102658, + "CreatedBy": "/bin/bash" + }, + { + "Id": "27cf78414709", + "Created": 1364068391, + "CreatedBy": "" + } + ] + + return status_code, response + + +def post_fake_import_image(): + status_code = 200 + response = 'Import messages...' + + return status_code, response + + +def get_fake_containers(): + status_code = 200 + response = [{ + 'Id': FAKE_CONTAINER_ID, + 'Image': 'busybox:latest', + 'Created': '2 days ago', + 'Command': 'true', + 'Status': 'fake status' + }] + return status_code, response + + +def post_fake_start_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_resize_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_create_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def get_fake_inspect_container(tty=False): + status_code = 200 + response = { + 'Id': FAKE_CONTAINER_ID, + 'Config': {'Labels': {'foo': 'bar'}, 'Privileged': True, 'Tty': tty}, + 'ID': FAKE_CONTAINER_ID, + 'Image': 'busybox:latest', + 'Name': 'foobar', + "State": { + "Status": "running", + "Running": True, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-09-25T14:01:18.869545111+02:00", + "Ghost": False + }, + "HostConfig": { + "LogConfig": { + "Type": "json-file", + "Config": {} + }, + }, + "MacAddress": "02:42:ac:11:00:0a" + } + return status_code, response + + +def get_fake_inspect_image(): + status_code = 200 + response = { + 'Id': FAKE_IMAGE_ID, + 'Parent': "27cf784147099545", + 'Created': "2013-03-23T22:24:18.818426-07:00", + 'Container': FAKE_CONTAINER_ID, + 'Config': {'Labels': {'bar': 'foo'}}, + 'ContainerConfig': + { + "Hostname": "", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": False, + "AttachStdout": False, + "AttachStderr": False, + "PortSpecs": "", + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "Env": "", + "Cmd": ["/bin/bash"], + "Dns": "", + "Image": "base", + "Volumes": "", + "VolumesFrom": "", + "WorkingDir": "" + }, + 'Size': 6823592 + } + return status_code, response + + +def get_fake_insert_image(): + status_code = 200 + response = {'StatusCode': 0} + return status_code, response + + +def get_fake_wait(): + status_code = 200 + response = {'StatusCode': 0} + return status_code, response + + +def get_fake_logs(): + status_code = 200 + response = (b'\x01\x00\x00\x00\x00\x00\x00\x00' + b'\x02\x00\x00\x00\x00\x00\x00\x00' + b'\x01\x00\x00\x00\x00\x00\x00\x11Flowering Nights\n' + b'\x01\x00\x00\x00\x00\x00\x00\x10(Sakuya Iyazoi)\n') + return status_code, response + + +def get_fake_diff(): + status_code = 200 + response = [{'Path': '/test', 'Kind': 1}] + return status_code, response + + +def get_fake_events(): + status_code = 200 + response = [{'status': 'stop', 'id': FAKE_CONTAINER_ID, + 'from': FAKE_IMAGE_ID, 'time': 1423247867}] + return status_code, response + + +def get_fake_export(): + status_code = 200 + response = 'Byte Stream....' + return status_code, response + + +def post_fake_exec_create(): + status_code = 200 + response = {'Id': FAKE_EXEC_ID} + return status_code, response + + +def post_fake_exec_start(): + status_code = 200 + response = (b'\x01\x00\x00\x00\x00\x00\x00\x11bin\nboot\ndev\netc\n' + b'\x01\x00\x00\x00\x00\x00\x00\x12lib\nmnt\nproc\nroot\n' + b'\x01\x00\x00\x00\x00\x00\x00\x0csbin\nusr\nvar\n') + return status_code, response + + +def post_fake_exec_resize(): + status_code = 201 + return status_code, '' + + +def get_fake_exec_inspect(): + return 200, { + 'OpenStderr': True, + 'OpenStdout': True, + 'Container': get_fake_inspect_container()[1], + 'Running': False, + 'ProcessConfig': { + 'arguments': ['hello world'], + 'tty': False, + 'entrypoint': 'echo', + 'privileged': False, + 'user': '' + }, + 'ExitCode': 0, + 'ID': FAKE_EXEC_ID, + 'OpenStdin': False + } + + +def post_fake_stop_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_kill_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_pause_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_unpause_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_restart_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_rename_container(): + status_code = 204 + return status_code, None + + +def delete_fake_remove_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_image_create(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def delete_fake_remove_image(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def get_fake_get_image(): + status_code = 200 + response = 'Byte Stream....' + return status_code, response + + +def post_fake_load_image(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def post_fake_commit(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_push(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def post_fake_build_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_tag_image(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def get_fake_stats(): + status_code = 200 + response = fake_stat.OBJ + return status_code, response + + +def get_fake_top(): + return 200, { + 'Processes': [ + [ + 'root', + '26501', + '6907', + '0', + '10:32', + 'pts/55', + '00:00:00', + 'sleep 60', + ], + ], + 'Titles': [ + 'UID', + 'PID', + 'PPID', + 'C', + 'STIME', + 'TTY', + 'TIME', + 'CMD', + ], + } + + +def get_fake_volume_list(): + status_code = 200 + response = { + 'Volumes': [ + { + 'Name': 'perfectcherryblossom', + 'Driver': 'local', + 'Mountpoint': '/var/lib/docker/volumes/perfectcherryblossom', + 'Scope': 'local' + }, { + 'Name': 'subterraneananimism', + 'Driver': 'local', + 'Mountpoint': '/var/lib/docker/volumes/subterraneananimism', + 'Scope': 'local' + } + ] + } + return status_code, response + + +def get_fake_volume(): + status_code = 200 + response = { + 'Name': 'perfectcherryblossom', + 'Driver': 'local', + 'Mountpoint': '/var/lib/docker/volumes/perfectcherryblossom', + 'Labels': { + 'com.example.some-label': 'some-value' + }, + 'Scope': 'local' + } + return status_code, response + + +def fake_remove_volume(): + return 204, None + + +def post_fake_update_container(): + return 200, {'Warnings': []} + + +def post_fake_update_node(): + return 200, None + + +def post_fake_join_swarm(): + return 200, None + + +def get_fake_network_list(): + return 200, [{ + "Name": "bridge", + "Id": FAKE_NETWORK_ID, + "Scope": "local", + "Driver": "bridge", + "EnableIPv6": False, + "Internal": False, + "IPAM": { + "Driver": "default", + "Config": [ + { + "Subnet": "172.17.0.0/16" + } + ] + }, + "Containers": { + FAKE_CONTAINER_ID: { + "EndpointID": "ed2419a97c1d99", + "MacAddress": "02:42:ac:11:00:02", + "IPv4Address": "172.17.0.2/16", + "IPv6Address": "" + } + }, + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "1500" + } + }] + + +def get_fake_network(): + return 200, get_fake_network_list()[1][0] + + +def post_fake_network(): + return 201, {"Id": FAKE_NETWORK_ID, "Warnings": []} + + +def delete_fake_network(): + return 204, None + + +def post_fake_network_connect(): + return 200, None + + +def post_fake_network_disconnect(): + return 200, None + + +def post_fake_secret(): + status_code = 200 + response = {'ID': FAKE_SECRET_ID} + return status_code, response + + +# Maps real api url to fake response callback +prefix = 'http+docker://localhost' +if constants.IS_WINDOWS_PLATFORM: + prefix = 'http+docker://localnpipe' + +fake_responses = { + '{prefix}/version'.format(prefix=prefix): + get_fake_version, + '{prefix}/{CURRENT_VERSION}/version'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_version, + '{prefix}/{CURRENT_VERSION}/info'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_info, + '{prefix}/{CURRENT_VERSION}/auth'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_auth, + '{prefix}/{CURRENT_VERSION}/_ping'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_ping, + '{prefix}/{CURRENT_VERSION}/images/search'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_search, + '{prefix}/{CURRENT_VERSION}/images/json'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_images, + '{prefix}/{CURRENT_VERSION}/images/test_image/history'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_image_history, + '{prefix}/{CURRENT_VERSION}/images/create'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_import_image, + '{prefix}/{CURRENT_VERSION}/containers/json'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_containers, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/start'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_start_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/resize'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_resize_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/json'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_inspect_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/rename'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_rename_container, + '{prefix}/{CURRENT_VERSION}/images/e9aa60c60128/tag'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_tag_image, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/wait'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_wait, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/logs'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_logs, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/changes'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_diff, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/export'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_export, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/update'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_update_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/exec'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_exec_create, + '{prefix}/{CURRENT_VERSION}/exec/d5d177f121dc/start'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_exec_start, + '{prefix}/{CURRENT_VERSION}/exec/d5d177f121dc/json'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_exec_inspect, + '{prefix}/{CURRENT_VERSION}/exec/d5d177f121dc/resize'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_exec_resize, + + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/stats'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_stats, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/top'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_top, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/stop'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_stop_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/kill'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_kill_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/pause'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_pause_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/unpause'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_unpause_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b/restart'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_restart_container, + '{prefix}/{CURRENT_VERSION}/containers/3cc2351ab11b'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + delete_fake_remove_container, + '{prefix}/{CURRENT_VERSION}/images/create'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_image_create, + '{prefix}/{CURRENT_VERSION}/images/e9aa60c60128'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + delete_fake_remove_image, + '{prefix}/{CURRENT_VERSION}/images/e9aa60c60128/get'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_get_image, + '{prefix}/{CURRENT_VERSION}/images/load'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_load_image, + '{prefix}/{CURRENT_VERSION}/images/test_image/json'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_inspect_image, + '{prefix}/{CURRENT_VERSION}/images/test_image/insert'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_insert_image, + '{prefix}/{CURRENT_VERSION}/images/test_image/push'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_push, + '{prefix}/{CURRENT_VERSION}/commit'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_commit, + '{prefix}/{CURRENT_VERSION}/containers/create'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_create_container, + '{prefix}/{CURRENT_VERSION}/build'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_build_container, + '{prefix}/{CURRENT_VERSION}/events'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + get_fake_events, + ('{prefix}/{CURRENT_VERSION}/volumes'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION), 'GET'): + get_fake_volume_list, + ('{prefix}/{CURRENT_VERSION}/volumes/create'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION), 'POST'): + get_fake_volume, + ('{1}/{0}/volumes/{2}'.format( + CURRENT_VERSION, prefix, FAKE_VOLUME_NAME + ), 'GET'): + get_fake_volume, + ('{1}/{0}/volumes/{2}'.format( + CURRENT_VERSION, prefix, FAKE_VOLUME_NAME + ), 'DELETE'): + fake_remove_volume, + ('{1}/{0}/nodes/{2}/update?version=1'.format( + CURRENT_VERSION, prefix, FAKE_NODE_ID + ), 'POST'): + post_fake_update_node, + ('{prefix}/{CURRENT_VERSION}/swarm/join'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION), 'POST'): + post_fake_join_swarm, + ('{prefix}/{CURRENT_VERSION}/networks'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION), 'GET'): + get_fake_network_list, + ('{prefix}/{CURRENT_VERSION}/networks/create'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION), 'POST'): + post_fake_network, + ('{1}/{0}/networks/{2}'.format( + CURRENT_VERSION, prefix, FAKE_NETWORK_ID + ), 'GET'): + get_fake_network, + ('{1}/{0}/networks/{2}'.format( + CURRENT_VERSION, prefix, FAKE_NETWORK_ID + ), 'DELETE'): + delete_fake_network, + ('{1}/{0}/networks/{2}/connect'.format( + CURRENT_VERSION, prefix, FAKE_NETWORK_ID + ), 'POST'): + post_fake_network_connect, + ('{1}/{0}/networks/{2}/disconnect'.format( + CURRENT_VERSION, prefix, FAKE_NETWORK_ID + ), 'POST'): + post_fake_network_disconnect, + '{prefix}/{CURRENT_VERSION}/secrets/create'.format(prefix=prefix, CURRENT_VERSION=CURRENT_VERSION): + post_fake_secret, +} diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/fake_stat.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/fake_stat.py new file mode 100644 index 000000000..97547328b --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/fake_stat.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +OBJ = { + "read": "2015-02-11T19:20:46.667237763+02:00", + "network": { + "rx_bytes": 567224, + "rx_packets": 3773, + "rx_errors": 0, + "rx_dropped": 0, + "tx_bytes": 1176, + "tx_packets": 13, + "tx_errors": 0, + "tx_dropped": 0 + }, + "cpu_stats": { + "cpu_usage": { + "total_usage": 157260874053, + "percpu_usage": [ + 52196306950, + 24118413549, + 53292684398, + 27653469156 + ], + "usage_in_kernelmode": 37140000000, + "usage_in_usermode": 62140000000 + }, + "system_cpu_usage": 3.0881377e+14, + "throttling_data": { + "periods": 0, + "throttled_periods": 0, + "throttled_time": 0 + } + }, + "memory_stats": { + "usage": 179314688, + "max_usage": 258166784, + "stats": { + "active_anon": 90804224, + "active_file": 2195456, + "cache": 3096576, + "hierarchical_memory_limit": 1.844674407371e+19, + "inactive_anon": 85516288, + "inactive_file": 798720, + "mapped_file": 2646016, + "pgfault": 101034, + "pgmajfault": 1207, + "pgpgin": 115814, + "pgpgout": 75613, + "rss": 176218112, + "rss_huge": 12582912, + "total_active_anon": 90804224, + "total_active_file": 2195456, + "total_cache": 3096576, + "total_inactive_anon": 85516288, + "total_inactive_file": 798720, + "total_mapped_file": 2646016, + "total_pgfault": 101034, + "total_pgmajfault": 1207, + "total_pgpgin": 115814, + "total_pgpgout": 75613, + "total_rss": 176218112, + "total_rss_huge": 12582912, + "total_unevictable": 0, + "total_writeback": 0, + "unevictable": 0, + "writeback": 0 + }, + "failcnt": 0, + "limit": 8039038976 + }, + "blkio_stats": { + "io_service_bytes_recursive": [ + { + "major": 8, + "minor": 0, + "op": "Read", + "value": 72843264 + }, { + "major": 8, + "minor": 0, + "op": "Write", + "value": 4096 + }, { + "major": 8, + "minor": 0, + "op": "Sync", + "value": 4096 + }, { + "major": 8, + "minor": 0, + "op": "Async", + "value": 72843264 + }, { + "major": 8, + "minor": 0, + "op": "Total", + "value": 72847360 + } + ], + "io_serviced_recursive": [ + { + "major": 8, + "minor": 0, + "op": "Read", + "value": 10581 + }, { + "major": 8, + "minor": 0, + "op": "Write", + "value": 1 + }, { + "major": 8, + "minor": 0, + "op": "Sync", + "value": 1 + }, { + "major": 8, + "minor": 0, + "op": "Async", + "value": 10581 + }, { + "major": 8, + "minor": 0, + "op": "Total", + "value": 10582 + } + ], + "io_queue_recursive": [], + "io_service_time_recursive": [], + "io_wait_time_recursive": [], + "io_merged_recursive": [], + "io_time_recursive": [], + "sectors_recursive": [] + } +} diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/test_auth.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/test_auth.py new file mode 100644 index 000000000..b3b5a3512 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/test_auth.py @@ -0,0 +1,819 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import base64 +import json +import os +import os.path +import random +import shutil +import tempfile +import unittest +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api import auth, errors +from ansible_collections.community.docker.plugins.module_utils._api.credentials.errors import CredentialsNotFound +from ansible_collections.community.docker.plugins.module_utils._api.credentials.store import Store + +try: + from unittest import mock +except ImportError: + import mock + + +class RegressionTest(unittest.TestCase): + def test_803_urlsafe_encode(self): + auth_data = { + 'username': 'root', + 'password': 'GR?XGR?XGR?XGR?X' + } + encoded = auth.encode_header(auth_data) + assert b'/' not in encoded + assert b'_' in encoded + + +class ResolveRepositoryNameTest(unittest.TestCase): + def test_resolve_repository_name_hub_library_image(self): + assert auth.resolve_repository_name('image') == ( + 'docker.io', 'image' + ) + + def test_resolve_repository_name_dotted_hub_library_image(self): + assert auth.resolve_repository_name('image.valid') == ( + 'docker.io', 'image.valid' + ) + + def test_resolve_repository_name_hub_image(self): + assert auth.resolve_repository_name('username/image') == ( + 'docker.io', 'username/image' + ) + + def test_explicit_hub_index_library_image(self): + assert auth.resolve_repository_name('docker.io/image') == ( + 'docker.io', 'image' + ) + + def test_explicit_legacy_hub_index_library_image(self): + assert auth.resolve_repository_name('index.docker.io/image') == ( + 'docker.io', 'image' + ) + + def test_resolve_repository_name_private_registry(self): + assert auth.resolve_repository_name('my.registry.net/image') == ( + 'my.registry.net', 'image' + ) + + def test_resolve_repository_name_private_registry_with_port(self): + assert auth.resolve_repository_name('my.registry.net:5000/image') == ( + 'my.registry.net:5000', 'image' + ) + + def test_resolve_repository_name_private_registry_with_username(self): + assert auth.resolve_repository_name( + 'my.registry.net/username/image' + ) == ('my.registry.net', 'username/image') + + def test_resolve_repository_name_no_dots_but_port(self): + assert auth.resolve_repository_name('hostname:5000/image') == ( + 'hostname:5000', 'image' + ) + + def test_resolve_repository_name_no_dots_but_port_and_username(self): + assert auth.resolve_repository_name( + 'hostname:5000/username/image' + ) == ('hostname:5000', 'username/image') + + def test_resolve_repository_name_localhost(self): + assert auth.resolve_repository_name('localhost/image') == ( + 'localhost', 'image' + ) + + def test_resolve_repository_name_localhost_with_username(self): + assert auth.resolve_repository_name('localhost/username/image') == ( + 'localhost', 'username/image' + ) + + def test_invalid_index_name(self): + with pytest.raises(errors.InvalidRepository): + auth.resolve_repository_name('-gecko.com/image') + + +def encode_auth(auth_info): + return base64.b64encode( + auth_info.get('username', '').encode('utf-8') + b':' + + auth_info.get('password', '').encode('utf-8')) + + +class ResolveAuthTest(unittest.TestCase): + index_config = {'auth': encode_auth({'username': 'indexuser'})} + private_config = {'auth': encode_auth({'username': 'privateuser'})} + legacy_config = {'auth': encode_auth({'username': 'legacyauth'})} + + auth_config = auth.AuthConfig({ + 'auths': auth.parse_auth({ + 'https://index.docker.io/v1/': index_config, + 'my.registry.net': private_config, + 'http://legacy.registry.url/v1/': legacy_config, + }) + }) + + def test_resolve_authconfig_hostname_only(self): + assert auth.resolve_authconfig( + self.auth_config, 'my.registry.net' + )['username'] == 'privateuser' + + def test_resolve_authconfig_no_protocol(self): + assert auth.resolve_authconfig( + self.auth_config, 'my.registry.net/v1/' + )['username'] == 'privateuser' + + def test_resolve_authconfig_no_path(self): + assert auth.resolve_authconfig( + self.auth_config, 'http://my.registry.net' + )['username'] == 'privateuser' + + def test_resolve_authconfig_no_path_trailing_slash(self): + assert auth.resolve_authconfig( + self.auth_config, 'http://my.registry.net/' + )['username'] == 'privateuser' + + def test_resolve_authconfig_no_path_wrong_secure_proto(self): + assert auth.resolve_authconfig( + self.auth_config, 'https://my.registry.net' + )['username'] == 'privateuser' + + def test_resolve_authconfig_no_path_wrong_insecure_proto(self): + assert auth.resolve_authconfig( + self.auth_config, 'http://index.docker.io' + )['username'] == 'indexuser' + + def test_resolve_authconfig_path_wrong_proto(self): + assert auth.resolve_authconfig( + self.auth_config, 'https://my.registry.net/v1/' + )['username'] == 'privateuser' + + def test_resolve_authconfig_default_registry(self): + assert auth.resolve_authconfig( + self.auth_config + )['username'] == 'indexuser' + + def test_resolve_authconfig_default_explicit_none(self): + assert auth.resolve_authconfig( + self.auth_config, None + )['username'] == 'indexuser' + + def test_resolve_authconfig_fully_explicit(self): + assert auth.resolve_authconfig( + self.auth_config, 'http://my.registry.net/v1/' + )['username'] == 'privateuser' + + def test_resolve_authconfig_legacy_config(self): + assert auth.resolve_authconfig( + self.auth_config, 'legacy.registry.url' + )['username'] == 'legacyauth' + + def test_resolve_authconfig_no_match(self): + assert auth.resolve_authconfig( + self.auth_config, 'does.not.exist' + ) is None + + def test_resolve_registry_and_auth_library_image(self): + image = 'image' + assert auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'] == 'indexuser' + + def test_resolve_registry_and_auth_hub_image(self): + image = 'username/image' + assert auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'] == 'indexuser' + + def test_resolve_registry_and_auth_explicit_hub(self): + image = 'docker.io/username/image' + assert auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'] == 'indexuser' + + def test_resolve_registry_and_auth_explicit_legacy_hub(self): + image = 'index.docker.io/username/image' + assert auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'] == 'indexuser' + + def test_resolve_registry_and_auth_private_registry(self): + image = 'my.registry.net/image' + assert auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'] == 'privateuser' + + def test_resolve_registry_and_auth_unauthenticated_registry(self): + image = 'other.registry.net/image' + assert auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + ) is None + + def test_resolve_auth_with_empty_credstore_and_auth_dict(self): + auth_config = auth.AuthConfig({ + 'auths': auth.parse_auth({ + 'https://index.docker.io/v1/': self.index_config, + }), + 'credsStore': 'blackbox' + }) + with mock.patch( + 'ansible_collections.community.docker.plugins.module_utils._api.auth.AuthConfig._resolve_authconfig_credstore' + ) as m: + m.return_value = None + assert 'indexuser' == auth.resolve_authconfig( + auth_config, None + )['username'] + + +class LoadConfigTest(unittest.TestCase): + def test_load_config_no_file(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + cfg = auth.load_config(folder) + assert cfg is not None + + def test_load_legacy_config(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + cfg_path = os.path.join(folder, '.dockercfg') + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + with open(cfg_path, 'w') as f: + f.write('auth = {auth}\n'.format(auth=auth_)) + f.write('email = sakuya@scarlet.net') + + cfg = auth.load_config(cfg_path) + assert auth.resolve_authconfig(cfg) is not None + assert cfg.auths[auth.INDEX_NAME] is not None + cfg = cfg.auths[auth.INDEX_NAME] + assert cfg['username'] == 'sakuya' + assert cfg['password'] == 'izayoi' + assert cfg['email'] == 'sakuya@scarlet.net' + assert cfg.get('Auth') is None + + def test_load_json_config(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + cfg_path = os.path.join(folder, '.dockercfg') + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + email = 'sakuya@scarlet.net' + with open(cfg_path, 'w') as f: + json.dump( + {auth.INDEX_URL: {'auth': auth_, 'email': email}}, f + ) + cfg = auth.load_config(cfg_path) + assert auth.resolve_authconfig(cfg) is not None + assert cfg.auths[auth.INDEX_URL] is not None + cfg = cfg.auths[auth.INDEX_URL] + assert cfg['username'] == 'sakuya' + assert cfg['password'] == 'izayoi' + assert cfg['email'] == email + assert cfg.get('Auth') is None + + def test_load_modern_json_config(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + cfg_path = os.path.join(folder, 'config.json') + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + email = 'sakuya@scarlet.net' + with open(cfg_path, 'w') as f: + json.dump({ + 'auths': { + auth.INDEX_URL: { + 'auth': auth_, 'email': email + } + } + }, f) + cfg = auth.load_config(cfg_path) + assert auth.resolve_authconfig(cfg) is not None + assert cfg.auths[auth.INDEX_URL] is not None + cfg = cfg.auths[auth.INDEX_URL] + assert cfg['username'] == 'sakuya' + assert cfg['password'] == 'izayoi' + assert cfg['email'] == email + + def test_load_config_with_random_name(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, + '.{0}.dockercfg'.format( + random.randrange(100000))) + registry = 'https://your.private.registry.io' + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + config = { + registry: { + 'auth': '{auth}'.format(auth=auth_), + 'email': 'sakuya@scarlet.net' + } + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + cfg = auth.load_config(dockercfg_path).auths + assert registry in cfg + assert cfg[registry] is not None + cfg = cfg[registry] + assert cfg['username'] == 'sakuya' + assert cfg['password'] == 'izayoi' + assert cfg['email'] == 'sakuya@scarlet.net' + assert cfg.get('auth') is None + + def test_load_config_custom_config_env(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, 'config.json') + registry = 'https://your.private.registry.io' + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + config = { + registry: { + 'auth': '{auth}'.format(auth=auth_), + 'email': 'sakuya@scarlet.net' + } + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): + cfg = auth.load_config(None).auths + assert registry in cfg + assert cfg[registry] is not None + cfg = cfg[registry] + assert cfg['username'] == 'sakuya' + assert cfg['password'] == 'izayoi' + assert cfg['email'] == 'sakuya@scarlet.net' + assert cfg.get('auth') is None + + def test_load_config_custom_config_env_with_auths(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, 'config.json') + registry = 'https://your.private.registry.io' + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + config = { + 'auths': { + registry: { + 'auth': '{auth}'.format(auth=auth_), + 'email': 'sakuya@scarlet.net' + } + } + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): + cfg = auth.load_config(None) + assert registry in cfg.auths + cfg = cfg.auths[registry] + assert cfg['username'] == 'sakuya' + assert cfg['password'] == 'izayoi' + assert cfg['email'] == 'sakuya@scarlet.net' + assert cfg.get('auth') is None + + def test_load_config_custom_config_env_utf8(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, 'config.json') + registry = 'https://your.private.registry.io' + auth_ = base64.b64encode( + b'sakuya\xc3\xa6:izayoi\xc3\xa6').decode('ascii') + config = { + 'auths': { + registry: { + 'auth': '{auth}'.format(auth=auth_), + 'email': 'sakuya@scarlet.net' + } + } + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): + cfg = auth.load_config(None) + assert registry in cfg.auths + cfg = cfg.auths[registry] + assert cfg['username'] == b'sakuya\xc3\xa6'.decode('utf8') + assert cfg['password'] == b'izayoi\xc3\xa6'.decode('utf8') + assert cfg['email'] == 'sakuya@scarlet.net' + assert cfg.get('auth') is None + + def test_load_config_unknown_keys(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + dockercfg_path = os.path.join(folder, 'config.json') + config = { + 'detachKeys': 'ctrl-q, ctrl-u, ctrl-i' + } + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + cfg = auth.load_config(dockercfg_path) + assert dict(cfg) == {'auths': {}} + + def test_load_config_invalid_auth_dict(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + dockercfg_path = os.path.join(folder, 'config.json') + config = { + 'auths': { + 'scarlet.net': {'sakuya': 'izayoi'} + } + } + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + cfg = auth.load_config(dockercfg_path) + assert dict(cfg) == {'auths': {'scarlet.net': {}}} + + def test_load_config_identity_token(self): + folder = tempfile.mkdtemp() + registry = 'scarlet.net' + token = '1ce1cebb-503e-7043-11aa-7feb8bd4a1ce' + self.addCleanup(shutil.rmtree, folder) + dockercfg_path = os.path.join(folder, 'config.json') + auth_entry = encode_auth({'username': 'sakuya'}).decode('ascii') + config = { + 'auths': { + registry: { + 'auth': auth_entry, + 'identitytoken': token + } + } + } + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + cfg = auth.load_config(dockercfg_path) + assert registry in cfg.auths + cfg = cfg.auths[registry] + assert 'IdentityToken' in cfg + assert cfg['IdentityToken'] == token + + +class CredstoreTest(unittest.TestCase): + def setUp(self): + self.authconfig = auth.AuthConfig({'credsStore': 'default'}) + self.default_store = InMemoryStore('default') + self.authconfig._stores['default'] = self.default_store + self.default_store.store( + 'https://gensokyo.jp/v2', 'sakuya', 'izayoi', + ) + self.default_store.store( + 'https://default.com/v2', 'user', 'hunter2', + ) + + def test_get_credential_store(self): + auth_config = auth.AuthConfig({ + 'credHelpers': { + 'registry1.io': 'truesecret', + 'registry2.io': 'powerlock' + }, + 'credsStore': 'blackbox', + }) + + assert auth_config.get_credential_store('registry1.io') == 'truesecret' + assert auth_config.get_credential_store('registry2.io') == 'powerlock' + assert auth_config.get_credential_store('registry3.io') == 'blackbox' + + def test_get_credential_store_no_default(self): + auth_config = auth.AuthConfig({ + 'credHelpers': { + 'registry1.io': 'truesecret', + 'registry2.io': 'powerlock' + }, + }) + assert auth_config.get_credential_store('registry2.io') == 'powerlock' + assert auth_config.get_credential_store('registry3.io') is None + + def test_get_credential_store_default_index(self): + auth_config = auth.AuthConfig({ + 'credHelpers': { + 'https://index.docker.io/v1/': 'powerlock' + }, + 'credsStore': 'truesecret' + }) + + assert auth_config.get_credential_store(None) == 'powerlock' + assert auth_config.get_credential_store('docker.io') == 'powerlock' + assert auth_config.get_credential_store('images.io') == 'truesecret' + + def test_get_credential_store_with_plain_dict(self): + auth_config = { + 'credHelpers': { + 'registry1.io': 'truesecret', + 'registry2.io': 'powerlock' + }, + 'credsStore': 'blackbox', + } + + assert auth.get_credential_store( + auth_config, 'registry1.io' + ) == 'truesecret' + assert auth.get_credential_store( + auth_config, 'registry2.io' + ) == 'powerlock' + assert auth.get_credential_store( + auth_config, 'registry3.io' + ) == 'blackbox' + + def test_get_all_credentials_credstore_only(self): + assert self.authconfig.get_all_credentials() == { + 'https://gensokyo.jp/v2': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'gensokyo.jp': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'https://default.com/v2': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'default.com': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + } + + def test_get_all_credentials_with_empty_credhelper(self): + self.authconfig['credHelpers'] = { + 'registry1.io': 'truesecret', + } + self.authconfig._stores['truesecret'] = InMemoryStore() + assert self.authconfig.get_all_credentials() == { + 'https://gensokyo.jp/v2': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'gensokyo.jp': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'https://default.com/v2': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'default.com': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'registry1.io': None, + } + + def test_get_all_credentials_with_credhelpers_only(self): + del self.authconfig['credsStore'] + assert self.authconfig.get_all_credentials() == {} + + self.authconfig['credHelpers'] = { + 'https://gensokyo.jp/v2': 'default', + 'https://default.com/v2': 'default', + } + + assert self.authconfig.get_all_credentials() == { + 'https://gensokyo.jp/v2': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'gensokyo.jp': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'https://default.com/v2': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'default.com': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + } + + def test_get_all_credentials_with_auths_entries(self): + self.authconfig.add_auth('registry1.io', { + 'ServerAddress': 'registry1.io', + 'Username': 'reimu', + 'Password': 'hakurei', + }) + + assert self.authconfig.get_all_credentials() == { + 'https://gensokyo.jp/v2': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'gensokyo.jp': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'https://default.com/v2': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'default.com': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'registry1.io': { + 'ServerAddress': 'registry1.io', + 'Username': 'reimu', + 'Password': 'hakurei', + }, + } + + def test_get_all_credentials_with_empty_auths_entry(self): + self.authconfig.add_auth('default.com', {}) + + assert self.authconfig.get_all_credentials() == { + 'https://gensokyo.jp/v2': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'gensokyo.jp': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'https://default.com/v2': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'default.com': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + } + + def test_get_all_credentials_credstore_overrides_auth_entry(self): + self.authconfig.add_auth('default.com', { + 'Username': 'shouldnotsee', + 'Password': 'thisentry', + 'ServerAddress': 'https://default.com/v2', + }) + + assert self.authconfig.get_all_credentials() == { + 'https://gensokyo.jp/v2': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'gensokyo.jp': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'https://default.com/v2': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'default.com': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + } + + def test_get_all_credentials_helpers_override_default(self): + self.authconfig['credHelpers'] = { + 'https://default.com/v2': 'truesecret', + } + truesecret = InMemoryStore('truesecret') + truesecret.store('https://default.com/v2', 'reimu', 'hakurei') + self.authconfig._stores['truesecret'] = truesecret + assert self.authconfig.get_all_credentials() == { + 'https://gensokyo.jp/v2': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'gensokyo.jp': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'https://default.com/v2': { + 'Username': 'reimu', + 'Password': 'hakurei', + 'ServerAddress': 'https://default.com/v2', + }, + 'default.com': { + 'Username': 'reimu', + 'Password': 'hakurei', + 'ServerAddress': 'https://default.com/v2', + }, + } + + def test_get_all_credentials_3_sources(self): + self.authconfig['credHelpers'] = { + 'registry1.io': 'truesecret', + } + truesecret = InMemoryStore('truesecret') + truesecret.store('registry1.io', 'reimu', 'hakurei') + self.authconfig._stores['truesecret'] = truesecret + self.authconfig.add_auth('registry2.io', { + 'ServerAddress': 'registry2.io', + 'Username': 'reimu', + 'Password': 'hakurei', + }) + + assert self.authconfig.get_all_credentials() == { + 'https://gensokyo.jp/v2': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'gensokyo.jp': { + 'Username': 'sakuya', + 'Password': 'izayoi', + 'ServerAddress': 'https://gensokyo.jp/v2', + }, + 'https://default.com/v2': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'default.com': { + 'Username': 'user', + 'Password': 'hunter2', + 'ServerAddress': 'https://default.com/v2', + }, + 'registry1.io': { + 'ServerAddress': 'registry1.io', + 'Username': 'reimu', + 'Password': 'hakurei', + }, + 'registry2.io': { + 'ServerAddress': 'registry2.io', + 'Username': 'reimu', + 'Password': 'hakurei', + } + } + + +class InMemoryStore(Store): + def __init__(self, *args, **kwargs): + self.__store = {} + + def get(self, server): + try: + return self.__store[server] + except KeyError: + raise CredentialsNotFound() + + def store(self, server, username, secret): + self.__store[server] = { + 'ServerURL': server, + 'Username': username, + 'Secret': secret, + } + + def list(self): + return dict( + (k, v['Username']) for k, v in self.__store.items() + ) + + def erase(self, server): + del self.__store[server] diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/test_errors.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/test_errors.py new file mode 100644 index 000000000..2cc114ed3 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/test_errors.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import unittest +import sys + +import pytest +import requests + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.errors import ( + APIError, DockerException, + create_unexpected_kwargs_error, + create_api_error_from_http_exception, +) + + +class APIErrorTest(unittest.TestCase): + def test_api_error_is_caught_by_dockerexception(self): + try: + raise APIError("this should be caught by DockerException") + except DockerException: + pass + + def test_status_code_200(self): + """The status_code property is present with 200 response.""" + resp = requests.Response() + resp.status_code = 200 + err = APIError('', response=resp) + assert err.status_code == 200 + + def test_status_code_400(self): + """The status_code property is present with 400 response.""" + resp = requests.Response() + resp.status_code = 400 + err = APIError('', response=resp) + assert err.status_code == 400 + + def test_status_code_500(self): + """The status_code property is present with 500 response.""" + resp = requests.Response() + resp.status_code = 500 + err = APIError('', response=resp) + assert err.status_code == 500 + + def test_is_server_error_200(self): + """Report not server error on 200 response.""" + resp = requests.Response() + resp.status_code = 200 + err = APIError('', response=resp) + assert err.is_server_error() is False + + def test_is_server_error_300(self): + """Report not server error on 300 response.""" + resp = requests.Response() + resp.status_code = 300 + err = APIError('', response=resp) + assert err.is_server_error() is False + + def test_is_server_error_400(self): + """Report not server error on 400 response.""" + resp = requests.Response() + resp.status_code = 400 + err = APIError('', response=resp) + assert err.is_server_error() is False + + def test_is_server_error_500(self): + """Report server error on 500 response.""" + resp = requests.Response() + resp.status_code = 500 + err = APIError('', response=resp) + assert err.is_server_error() is True + + def test_is_client_error_500(self): + """Report not client error on 500 response.""" + resp = requests.Response() + resp.status_code = 500 + err = APIError('', response=resp) + assert err.is_client_error() is False + + def test_is_client_error_400(self): + """Report client error on 400 response.""" + resp = requests.Response() + resp.status_code = 400 + err = APIError('', response=resp) + assert err.is_client_error() is True + + def test_is_error_300(self): + """Report no error on 300 response.""" + resp = requests.Response() + resp.status_code = 300 + err = APIError('', response=resp) + assert err.is_error() is False + + def test_is_error_400(self): + """Report error on 400 response.""" + resp = requests.Response() + resp.status_code = 400 + err = APIError('', response=resp) + assert err.is_error() is True + + def test_is_error_500(self): + """Report error on 500 response.""" + resp = requests.Response() + resp.status_code = 500 + err = APIError('', response=resp) + assert err.is_error() is True + + def test_create_error_from_exception(self): + resp = requests.Response() + resp.status_code = 500 + err = APIError('') + try: + resp.raise_for_status() + except requests.exceptions.HTTPError as e: + try: + create_api_error_from_http_exception(e) + except APIError as e: + err = e + assert err.is_server_error() is True + + +class CreateUnexpectedKwargsErrorTest(unittest.TestCase): + def test_create_unexpected_kwargs_error_single(self): + e = create_unexpected_kwargs_error('f', {'foo': 'bar'}) + assert str(e) == "f() got an unexpected keyword argument 'foo'" + + def test_create_unexpected_kwargs_error_multiple(self): + e = create_unexpected_kwargs_error('f', {'foo': 'bar', 'baz': 'bosh'}) + assert str(e) == "f() got unexpected keyword arguments 'baz', 'foo'" diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/transport/test_sshconn.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/transport/test_sshconn.py new file mode 100644 index 000000000..e9189f3e6 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/transport/test_sshconn.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import unittest +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.transport.sshconn import SSHSocket, SSHHTTPAdapter + + +class SSHAdapterTest(unittest.TestCase): + @staticmethod + def test_ssh_hostname_prefix_trim(): + conn = SSHHTTPAdapter( + base_url="ssh://user@hostname:1234", shell_out=True) + assert conn.ssh_host == "user@hostname:1234" + + @staticmethod + def test_ssh_parse_url(): + c = SSHSocket(host="user@hostname:1234") + assert c.host == "hostname" + assert c.port == "1234" + assert c.user == "user" + + @staticmethod + def test_ssh_parse_hostname_only(): + c = SSHSocket(host="hostname") + assert c.host == "hostname" + assert c.port is None + assert c.user is None + + @staticmethod + def test_ssh_parse_user_and_hostname(): + c = SSHSocket(host="user@hostname") + assert c.host == "hostname" + assert c.port is None + assert c.user == "user" + + @staticmethod + def test_ssh_parse_hostname_and_port(): + c = SSHSocket(host="hostname:22") + assert c.host == "hostname" + assert c.port == "22" + assert c.user is None diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/transport/test_ssladapter.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/transport/test_ssladapter.py new file mode 100644 index 000000000..428163e61 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/transport/test_ssladapter.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import unittest +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.transport import ssladapter + +try: + from backports.ssl_match_hostname import ( + match_hostname, CertificateError + ) +except ImportError: + from ssl import ( + match_hostname, CertificateError + ) + +try: + from ssl import OP_NO_SSLv3, OP_NO_SSLv2, OP_NO_TLSv1 +except ImportError: + OP_NO_SSLv2 = 0x1000000 + OP_NO_SSLv3 = 0x2000000 + OP_NO_TLSv1 = 0x4000000 + + +class SSLAdapterTest(unittest.TestCase): + def test_only_uses_tls(self): + ssl_context = ssladapter.urllib3.util.ssl_.create_urllib3_context() + + assert ssl_context.options & OP_NO_SSLv3 + # if OpenSSL is compiled without SSL2 support, OP_NO_SSLv2 will be 0 + assert not bool(OP_NO_SSLv2) or ssl_context.options & OP_NO_SSLv2 + assert not ssl_context.options & OP_NO_TLSv1 + + +class MatchHostnameTest(unittest.TestCase): + cert = { + 'issuer': ( + (('countryName', 'US'),), + (('stateOrProvinceName', 'California'),), + (('localityName', 'San Francisco'),), + (('organizationName', 'Docker Inc'),), + (('organizationalUnitName', 'Docker-Python'),), + (('commonName', 'localhost'),), + (('emailAddress', 'info@docker.com'),) + ), + 'notAfter': 'Mar 25 23:08:23 2030 GMT', + 'notBefore': 'Mar 25 23:08:23 2016 GMT', + 'serialNumber': 'BD5F894C839C548F', + 'subject': ( + (('countryName', 'US'),), + (('stateOrProvinceName', 'California'),), + (('localityName', 'San Francisco'),), + (('organizationName', 'Docker Inc'),), + (('organizationalUnitName', 'Docker-Python'),), + (('commonName', 'localhost'),), + (('emailAddress', 'info@docker.com'),) + ), + 'subjectAltName': ( + ('DNS', 'localhost'), + ('DNS', '*.gensokyo.jp'), + ('IP Address', '127.0.0.1'), + ), + 'version': 3 + } + + def test_match_ip_address_success(self): + assert match_hostname(self.cert, '127.0.0.1') is None + + def test_match_localhost_success(self): + assert match_hostname(self.cert, 'localhost') is None + + def test_match_dns_success(self): + assert match_hostname(self.cert, 'touhou.gensokyo.jp') is None + + def test_match_ip_address_failure(self): + with pytest.raises(CertificateError): + match_hostname(self.cert, '192.168.0.25') + + def test_match_dns_failure(self): + with pytest.raises(CertificateError): + match_hostname(self.cert, 'foobar.co.uk') diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_build.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_build.py new file mode 100644 index 000000000..50eb703d0 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_build.py @@ -0,0 +1,515 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import os.path +import shutil +import socket +import tarfile +import tempfile +import unittest +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.constants import IS_WINDOWS_PLATFORM +from ansible_collections.community.docker.plugins.module_utils._api.utils.build import exclude_paths, tar + + +def make_tree(dirs, files): + base = tempfile.mkdtemp() + + for path in dirs: + os.makedirs(os.path.join(base, path)) + + for path in files: + with open(os.path.join(base, path), 'w') as f: + f.write("content") + + return base + + +def convert_paths(collection): + return set(map(convert_path, collection)) + + +def convert_path(path): + return path.replace('/', os.path.sep) + + +class ExcludePathsTest(unittest.TestCase): + dirs = [ + 'foo', + 'foo/bar', + 'bar', + 'target', + 'target/subdir', + 'subdir', + 'subdir/target', + 'subdir/target/subdir', + 'subdir/subdir2', + 'subdir/subdir2/target', + 'subdir/subdir2/target/subdir' + ] + + files = [ + 'Dockerfile', + 'Dockerfile.alt', + '.dockerignore', + 'a.py', + 'a.go', + 'b.py', + 'cde.py', + 'foo/a.py', + 'foo/b.py', + 'foo/bar/a.py', + 'bar/a.py', + 'foo/Dockerfile3', + 'target/file.txt', + 'target/subdir/file.txt', + 'subdir/file.txt', + 'subdir/target/file.txt', + 'subdir/target/subdir/file.txt', + 'subdir/subdir2/file.txt', + 'subdir/subdir2/target/file.txt', + 'subdir/subdir2/target/subdir/file.txt', + ] + + all_paths = set(dirs + files) + + def setUp(self): + self.base = make_tree(self.dirs, self.files) + + def tearDown(self): + shutil.rmtree(self.base) + + def exclude(self, patterns, dockerfile=None): + return set(exclude_paths(self.base, patterns, dockerfile=dockerfile)) + + def test_no_excludes(self): + assert self.exclude(['']) == convert_paths(self.all_paths) + + def test_no_dupes(self): + paths = exclude_paths(self.base, ['!a.py']) + assert sorted(paths) == sorted(set(paths)) + + def test_wildcard_exclude(self): + assert self.exclude(['*']) == set(['Dockerfile', '.dockerignore']) + + def test_exclude_dockerfile_dockerignore(self): + """ + Even if the .dockerignore file explicitly says to exclude + Dockerfile and/or .dockerignore, don't exclude them from + the actual tar file. + """ + assert self.exclude(['Dockerfile', '.dockerignore']) == convert_paths( + self.all_paths + ) + + def test_exclude_custom_dockerfile(self): + """ + If we're using a custom Dockerfile, make sure that's not + excluded. + """ + assert self.exclude(['*'], dockerfile='Dockerfile.alt') == set(['Dockerfile.alt', '.dockerignore']) + + assert self.exclude( + ['*'], dockerfile='foo/Dockerfile3' + ) == convert_paths(set(['foo/Dockerfile3', '.dockerignore'])) + + # https://github.com/docker/docker-py/issues/1956 + assert self.exclude( + ['*'], dockerfile='./foo/Dockerfile3' + ) == convert_paths(set(['foo/Dockerfile3', '.dockerignore'])) + + def test_exclude_dockerfile_child(self): + includes = self.exclude(['foo/'], dockerfile='foo/Dockerfile3') + assert convert_path('foo/Dockerfile3') in includes + assert convert_path('foo/a.py') not in includes + + def test_single_filename(self): + assert self.exclude(['a.py']) == convert_paths( + self.all_paths - set(['a.py']) + ) + + def test_single_filename_leading_dot_slash(self): + assert self.exclude(['./a.py']) == convert_paths( + self.all_paths - set(['a.py']) + ) + + # As odd as it sounds, a filename pattern with a trailing slash on the + # end *will* result in that file being excluded. + def test_single_filename_trailing_slash(self): + assert self.exclude(['a.py/']) == convert_paths( + self.all_paths - set(['a.py']) + ) + + def test_wildcard_filename_start(self): + assert self.exclude(['*.py']) == convert_paths( + self.all_paths - set(['a.py', 'b.py', 'cde.py']) + ) + + def test_wildcard_with_exception(self): + assert self.exclude(['*.py', '!b.py']) == convert_paths( + self.all_paths - set(['a.py', 'cde.py']) + ) + + def test_wildcard_with_wildcard_exception(self): + assert self.exclude(['*.*', '!*.go']) == convert_paths( + self.all_paths - set([ + 'a.py', 'b.py', 'cde.py', 'Dockerfile.alt', + ]) + ) + + def test_wildcard_filename_end(self): + assert self.exclude(['a.*']) == convert_paths( + self.all_paths - set(['a.py', 'a.go']) + ) + + def test_question_mark(self): + assert self.exclude(['?.py']) == convert_paths( + self.all_paths - set(['a.py', 'b.py']) + ) + + def test_single_subdir_single_filename(self): + assert self.exclude(['foo/a.py']) == convert_paths( + self.all_paths - set(['foo/a.py']) + ) + + def test_single_subdir_single_filename_leading_slash(self): + assert self.exclude(['/foo/a.py']) == convert_paths( + self.all_paths - set(['foo/a.py']) + ) + + def test_exclude_include_absolute_path(self): + base = make_tree([], ['a.py', 'b.py']) + assert exclude_paths( + base, + ['/*', '!/*.py'] + ) == set(['a.py', 'b.py']) + + def test_single_subdir_with_path_traversal(self): + assert self.exclude(['foo/whoops/../a.py']) == convert_paths( + self.all_paths - set(['foo/a.py']) + ) + + def test_single_subdir_wildcard_filename(self): + assert self.exclude(['foo/*.py']) == convert_paths( + self.all_paths - set(['foo/a.py', 'foo/b.py']) + ) + + def test_wildcard_subdir_single_filename(self): + assert self.exclude(['*/a.py']) == convert_paths( + self.all_paths - set(['foo/a.py', 'bar/a.py']) + ) + + def test_wildcard_subdir_wildcard_filename(self): + assert self.exclude(['*/*.py']) == convert_paths( + self.all_paths - set(['foo/a.py', 'foo/b.py', 'bar/a.py']) + ) + + def test_directory(self): + assert self.exclude(['foo']) == convert_paths( + self.all_paths - set([ + 'foo', 'foo/a.py', 'foo/b.py', 'foo/bar', 'foo/bar/a.py', + 'foo/Dockerfile3' + ]) + ) + + def test_directory_with_trailing_slash(self): + assert self.exclude(['foo']) == convert_paths( + self.all_paths - set([ + 'foo', 'foo/a.py', 'foo/b.py', + 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' + ]) + ) + + def test_directory_with_single_exception(self): + assert self.exclude(['foo', '!foo/bar/a.py']) == convert_paths( + self.all_paths - set([ + 'foo/a.py', 'foo/b.py', 'foo', 'foo/bar', + 'foo/Dockerfile3' + ]) + ) + + def test_directory_with_subdir_exception(self): + assert self.exclude(['foo', '!foo/bar']) == convert_paths( + self.all_paths - set([ + 'foo/a.py', 'foo/b.py', 'foo', 'foo/Dockerfile3' + ]) + ) + + @pytest.mark.skipif( + not IS_WINDOWS_PLATFORM, reason='Backslash patterns only on Windows' + ) + def test_directory_with_subdir_exception_win32_pathsep(self): + assert self.exclude(['foo', '!foo\\bar']) == convert_paths( + self.all_paths - set([ + 'foo/a.py', 'foo/b.py', 'foo', 'foo/Dockerfile3' + ]) + ) + + def test_directory_with_wildcard_exception(self): + assert self.exclude(['foo', '!foo/*.py']) == convert_paths( + self.all_paths - set([ + 'foo/bar', 'foo/bar/a.py', 'foo', 'foo/Dockerfile3' + ]) + ) + + def test_subdirectory(self): + assert self.exclude(['foo/bar']) == convert_paths( + self.all_paths - set(['foo/bar', 'foo/bar/a.py']) + ) + + @pytest.mark.skipif( + not IS_WINDOWS_PLATFORM, reason='Backslash patterns only on Windows' + ) + def test_subdirectory_win32_pathsep(self): + assert self.exclude(['foo\\bar']) == convert_paths( + self.all_paths - set(['foo/bar', 'foo/bar/a.py']) + ) + + def test_double_wildcard(self): + assert self.exclude(['**/a.py']) == convert_paths( + self.all_paths - set([ + 'a.py', 'foo/a.py', 'foo/bar/a.py', 'bar/a.py' + ]) + ) + + assert self.exclude(['foo/**/bar']) == convert_paths( + self.all_paths - set(['foo/bar', 'foo/bar/a.py']) + ) + + def test_single_and_double_wildcard(self): + assert self.exclude(['**/target/*/*']) == convert_paths( + self.all_paths - set([ + 'target/subdir/file.txt', + 'subdir/target/subdir/file.txt', + 'subdir/subdir2/target/subdir/file.txt', + ]) + ) + + def test_trailing_double_wildcard(self): + assert self.exclude(['subdir/**']) == convert_paths( + self.all_paths - set([ + 'subdir/file.txt', + 'subdir/target/file.txt', + 'subdir/target/subdir/file.txt', + 'subdir/subdir2/file.txt', + 'subdir/subdir2/target/file.txt', + 'subdir/subdir2/target/subdir/file.txt', + 'subdir/target', + 'subdir/target/subdir', + 'subdir/subdir2', + 'subdir/subdir2/target', + 'subdir/subdir2/target/subdir', + ]) + ) + + def test_double_wildcard_with_exception(self): + assert self.exclude(['**', '!bar', '!foo/bar']) == convert_paths( + set([ + 'foo/bar', 'foo/bar/a.py', 'bar', 'bar/a.py', 'Dockerfile', + '.dockerignore', + ]) + ) + + def test_include_wildcard(self): + # This may be surprising but it matches the CLI's behavior + # (tested with 18.05.0-ce on linux) + base = make_tree(['a'], ['a/b.py']) + assert exclude_paths( + base, + ['*', '!*/b.py'] + ) == set() + + def test_last_line_precedence(self): + base = make_tree( + [], + ['garbage.md', + 'trash.md', + 'README.md', + 'README-bis.md', + 'README-secret.md']) + assert exclude_paths( + base, + ['*.md', '!README*.md', 'README-secret.md'] + ) == set(['README.md', 'README-bis.md']) + + def test_parent_directory(self): + base = make_tree( + [], + ['a.py', + 'b.py', + 'c.py']) + # Dockerignore reference stipulates that absolute paths are + # equivalent to relative paths, hence /../foo should be + # equivalent to ../foo. It also stipulates that paths are run + # through Go's filepath.Clean, which explicitly "replace + # "/.." by "/" at the beginning of a path". + assert exclude_paths( + base, + ['../a.py', '/../b.py'] + ) == set(['c.py']) + + +class TarTest(unittest.TestCase): + def test_tar_with_excludes(self): + dirs = [ + 'foo', + 'foo/bar', + 'bar', + ] + + files = [ + 'Dockerfile', + 'Dockerfile.alt', + '.dockerignore', + 'a.py', + 'a.go', + 'b.py', + 'cde.py', + 'foo/a.py', + 'foo/b.py', + 'foo/bar/a.py', + 'bar/a.py', + ] + + exclude = [ + '*.py', + '!b.py', + '!a.go', + 'foo', + 'Dockerfile*', + '.dockerignore', + ] + + expected_names = set([ + 'Dockerfile', + '.dockerignore', + 'a.go', + 'b.py', + 'bar', + 'bar/a.py', + ]) + + base = make_tree(dirs, files) + self.addCleanup(shutil.rmtree, base) + + with tar(base, exclude=exclude) as archive: + tar_data = tarfile.open(fileobj=archive) + assert sorted(tar_data.getnames()) == sorted(expected_names) + + def test_tar_with_empty_directory(self): + base = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, base) + for d in ['foo', 'bar']: + os.makedirs(os.path.join(base, d)) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + assert sorted(tar_data.getnames()) == ['bar', 'foo'] + + @pytest.mark.skipif( + IS_WINDOWS_PLATFORM or os.geteuid() == 0, + reason='root user always has access ; no chmod on Windows' + ) + def test_tar_with_inaccessible_file(self): + base = tempfile.mkdtemp() + full_path = os.path.join(base, 'foo') + self.addCleanup(shutil.rmtree, base) + with open(full_path, 'w') as f: + f.write('content') + os.chmod(full_path, 0o222) + with pytest.raises(IOError) as ei: + tar(base) + + assert 'Can not read file in context: {full_path}'.format(full_path=full_path) in ( + ei.exconly() + ) + + @pytest.mark.skipif(IS_WINDOWS_PLATFORM, reason='No symlinks on Windows') + def test_tar_with_file_symlinks(self): + base = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, base) + with open(os.path.join(base, 'foo'), 'w') as f: + f.write("content") + os.makedirs(os.path.join(base, 'bar')) + os.symlink('../foo', os.path.join(base, 'bar/foo')) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + assert sorted(tar_data.getnames()) == ['bar', 'bar/foo', 'foo'] + + @pytest.mark.skipif(IS_WINDOWS_PLATFORM, reason='No symlinks on Windows') + def test_tar_with_directory_symlinks(self): + base = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, base) + for d in ['foo', 'bar']: + os.makedirs(os.path.join(base, d)) + os.symlink('../foo', os.path.join(base, 'bar/foo')) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + assert sorted(tar_data.getnames()) == ['bar', 'bar/foo', 'foo'] + + @pytest.mark.skipif(IS_WINDOWS_PLATFORM, reason='No symlinks on Windows') + def test_tar_with_broken_symlinks(self): + base = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, base) + for d in ['foo', 'bar']: + os.makedirs(os.path.join(base, d)) + + os.symlink('../baz', os.path.join(base, 'bar/foo')) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + assert sorted(tar_data.getnames()) == ['bar', 'bar/foo', 'foo'] + + @pytest.mark.skipif(IS_WINDOWS_PLATFORM, reason='No UNIX sockets on Win32') + def test_tar_socket_file(self): + base = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, base) + for d in ['foo', 'bar']: + os.makedirs(os.path.join(base, d)) + sock = socket.socket(socket.AF_UNIX) + self.addCleanup(sock.close) + sock.bind(os.path.join(base, 'test.sock')) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + assert sorted(tar_data.getnames()) == ['bar', 'foo'] + + def tar_test_negative_mtime_bug(self): + base = tempfile.mkdtemp() + filename = os.path.join(base, 'th.txt') + self.addCleanup(shutil.rmtree, base) + with open(filename, 'w') as f: + f.write('Invisible Full Moon') + os.utime(filename, (12345, -3600.0)) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + assert tar_data.getnames() == ['th.txt'] + assert tar_data.getmember('th.txt').mtime == -3600 + + @pytest.mark.skipif(IS_WINDOWS_PLATFORM, reason='No symlinks on Windows') + def test_tar_directory_link(self): + dirs = ['a', 'b', 'a/c'] + files = ['a/hello.py', 'b/utils.py', 'a/c/descend.py'] + base = make_tree(dirs, files) + self.addCleanup(shutil.rmtree, base) + os.symlink(os.path.join(base, 'b'), os.path.join(base, 'a/c/b')) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + names = tar_data.getnames() + for member in dirs + files: + assert member in names + assert 'a/c/b' in names + assert 'a/c/b/utils.py' not in names diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_config.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_config.py new file mode 100644 index 000000000..9448f3849 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_config.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import unittest +import shutil +import tempfile +import json +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from pytest import mark, fixture + +from ansible_collections.community.docker.plugins.module_utils._api.utils import config + +try: + from unittest import mock +except ImportError: + import mock + + +class FindConfigFileTest(unittest.TestCase): + + @fixture(autouse=True) + def tmpdir(self, tmpdir): + self.mkdir = tmpdir.mkdir + + def test_find_config_fallback(self): + tmpdir = self.mkdir('test_find_config_fallback') + + with mock.patch.dict(os.environ, {'HOME': str(tmpdir)}): + assert config.find_config_file() is None + + def test_find_config_from_explicit_path(self): + tmpdir = self.mkdir('test_find_config_from_explicit_path') + config_path = tmpdir.ensure('my-config-file.json') + + assert config.find_config_file(str(config_path)) == str(config_path) + + def test_find_config_from_environment(self): + tmpdir = self.mkdir('test_find_config_from_environment') + config_path = tmpdir.ensure('config.json') + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': str(tmpdir)}): + assert config.find_config_file() == str(config_path) + + @mark.skipif("sys.platform == 'win32'") + def test_find_config_from_home_posix(self): + tmpdir = self.mkdir('test_find_config_from_home_posix') + config_path = tmpdir.ensure('.docker', 'config.json') + + with mock.patch.dict(os.environ, {'HOME': str(tmpdir)}): + assert config.find_config_file() == str(config_path) + + @mark.skipif("sys.platform == 'win32'") + def test_find_config_from_home_legacy_name(self): + tmpdir = self.mkdir('test_find_config_from_home_legacy_name') + config_path = tmpdir.ensure('.dockercfg') + + with mock.patch.dict(os.environ, {'HOME': str(tmpdir)}): + assert config.find_config_file() == str(config_path) + + @mark.skipif("sys.platform != 'win32'") + def test_find_config_from_home_windows(self): + tmpdir = self.mkdir('test_find_config_from_home_windows') + config_path = tmpdir.ensure('.docker', 'config.json') + + with mock.patch.dict(os.environ, {'USERPROFILE': str(tmpdir)}): + assert config.find_config_file() == str(config_path) + + +class LoadConfigTest(unittest.TestCase): + def test_load_config_no_file(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + cfg = config.load_general_config(folder) + assert cfg is not None + assert isinstance(cfg, dict) + assert not cfg + + def test_load_config_custom_headers(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, 'config.json') + config_data = { + 'HttpHeaders': { + 'Name': 'Spike', + 'Surname': 'Spiegel' + }, + } + + with open(dockercfg_path, 'w') as f: + json.dump(config_data, f) + + cfg = config.load_general_config(dockercfg_path) + assert 'HttpHeaders' in cfg + assert cfg['HttpHeaders'] == { + 'Name': 'Spike', + 'Surname': 'Spiegel' + } + + def test_load_config_detach_keys(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + dockercfg_path = os.path.join(folder, 'config.json') + config_data = { + 'detachKeys': 'ctrl-q, ctrl-u, ctrl-i' + } + with open(dockercfg_path, 'w') as f: + json.dump(config_data, f) + + cfg = config.load_general_config(dockercfg_path) + assert cfg == config_data + + def test_load_config_from_env(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + dockercfg_path = os.path.join(folder, 'config.json') + config_data = { + 'detachKeys': 'ctrl-q, ctrl-u, ctrl-i' + } + with open(dockercfg_path, 'w') as f: + json.dump(config_data, f) + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): + cfg = config.load_general_config(None) + assert cfg == config_data diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_decorators.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_decorators.py new file mode 100644 index 000000000..8ba1ec5f0 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_decorators.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import unittest +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.api.client import APIClient +from ansible_collections.community.docker.plugins.module_utils._api.constants import DEFAULT_DOCKER_API_VERSION +from ansible_collections.community.docker.plugins.module_utils._api.utils.decorators import update_headers + + +class DecoratorsTest(unittest.TestCase): + def test_update_headers(self): + sample_headers = { + 'X-Docker-Locale': 'en-US', + } + + def f(self, headers=None): + return headers + + client = APIClient(version=DEFAULT_DOCKER_API_VERSION) + client._general_configs = {} + + g = update_headers(f) + assert g(client, headers=None) is None + assert g(client, headers={}) == {} + assert g(client, headers={'Content-type': 'application/json'}) == { + 'Content-type': 'application/json', + } + + client._general_configs = { + 'HttpHeaders': sample_headers + } + + assert g(client, headers=None) == sample_headers + assert g(client, headers={}) == sample_headers + assert g(client, headers={'Content-type': 'application/json'}) == { + 'Content-type': 'application/json', + 'X-Docker-Locale': 'en-US', + } diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_json_stream.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_json_stream.py new file mode 100644 index 000000000..2d7f300fa --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_json_stream.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.utils.json_stream import json_splitter, stream_as_text, json_stream + + +class TestJsonSplitter: + + def test_json_splitter_no_object(self): + data = '{"foo": "bar' + assert json_splitter(data) is None + + def test_json_splitter_with_object(self): + data = '{"foo": "bar"}\n \n{"next": "obj"}' + assert json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}') + + def test_json_splitter_leading_whitespace(self): + data = '\n \r{"foo": "bar"}\n\n {"next": "obj"}' + assert json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}') + + +class TestStreamAsText: + + def test_stream_with_non_utf_unicode_character(self): + stream = [b'\xed\xf3\xf3'] + output, = stream_as_text(stream) + assert output == u'���' + + def test_stream_with_utf_character(self): + stream = [u'ěĝ'.encode('utf-8')] + output, = stream_as_text(stream) + assert output == u'ěĝ' + + +class TestJsonStream: + + def test_with_falsy_entries(self): + stream = [ + '{"one": "two"}\n{}\n', + "[1, 2, 3]\n[]\n", + ] + output = list(json_stream(stream)) + assert output == [ + {'one': 'two'}, + {}, + [1, 2, 3], + [], + ] + + def test_with_leading_whitespace(self): + stream = [ + '\n \r\n {"one": "two"}{"x": 1}', + ' {"three": "four"}\t\t{"x": 2}' + ] + output = list(json_stream(stream)) + assert output == [ + {'one': 'two'}, + {'x': 1}, + {'three': 'four'}, + {'x': 2} + ] diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_ports.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_ports.py new file mode 100644 index 000000000..c1a08a126 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_ports.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import unittest +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.utils.ports import build_port_bindings, split_port + + +class PortsTest(unittest.TestCase): + def test_split_port_with_host_ip(self): + internal_port, external_port = split_port("127.0.0.1:1000:2000") + assert internal_port == ["2000"] + assert external_port == [("127.0.0.1", "1000")] + + def test_split_port_with_protocol(self): + for protocol in ['tcp', 'udp', 'sctp']: + internal_port, external_port = split_port( + "127.0.0.1:1000:2000/" + protocol + ) + assert internal_port == ["2000/" + protocol] + assert external_port == [("127.0.0.1", "1000")] + + def test_split_port_with_host_ip_no_port(self): + internal_port, external_port = split_port("127.0.0.1::2000") + assert internal_port == ["2000"] + assert external_port == [("127.0.0.1", None)] + + def test_split_port_range_with_host_ip_no_port(self): + internal_port, external_port = split_port("127.0.0.1::2000-2001") + assert internal_port == ["2000", "2001"] + assert external_port == [("127.0.0.1", None), ("127.0.0.1", None)] + + def test_split_port_with_host_port(self): + internal_port, external_port = split_port("1000:2000") + assert internal_port == ["2000"] + assert external_port == ["1000"] + + def test_split_port_range_with_host_port(self): + internal_port, external_port = split_port("1000-1001:2000-2001") + assert internal_port == ["2000", "2001"] + assert external_port == ["1000", "1001"] + + def test_split_port_random_port_range_with_host_port(self): + internal_port, external_port = split_port("1000-1001:2000") + assert internal_port == ["2000"] + assert external_port == ["1000-1001"] + + def test_split_port_no_host_port(self): + internal_port, external_port = split_port("2000") + assert internal_port == ["2000"] + assert external_port is None + + def test_split_port_range_no_host_port(self): + internal_port, external_port = split_port("2000-2001") + assert internal_port == ["2000", "2001"] + assert external_port is None + + def test_split_port_range_with_protocol(self): + internal_port, external_port = split_port( + "127.0.0.1:1000-1001:2000-2001/udp") + assert internal_port == ["2000/udp", "2001/udp"] + assert external_port == [("127.0.0.1", "1000"), ("127.0.0.1", "1001")] + + def test_split_port_with_ipv6_address(self): + internal_port, external_port = split_port( + "2001:abcd:ef00::2:1000:2000") + assert internal_port == ["2000"] + assert external_port == [("2001:abcd:ef00::2", "1000")] + + def test_split_port_with_ipv6_square_brackets_address(self): + internal_port, external_port = split_port( + "[2001:abcd:ef00::2]:1000:2000") + assert internal_port == ["2000"] + assert external_port == [("2001:abcd:ef00::2", "1000")] + + def test_split_port_invalid(self): + with pytest.raises(ValueError): + split_port("0.0.0.0:1000:2000:tcp") + + def test_split_port_invalid_protocol(self): + with pytest.raises(ValueError): + split_port("0.0.0.0:1000:2000/ftp") + + def test_non_matching_length_port_ranges(self): + with pytest.raises(ValueError): + split_port("0.0.0.0:1000-1010:2000-2002/tcp") + + def test_port_and_range_invalid(self): + with pytest.raises(ValueError): + split_port("0.0.0.0:1000:2000-2002/tcp") + + def test_port_only_with_colon(self): + with pytest.raises(ValueError): + split_port(":80") + + def test_host_only_with_colon(self): + with pytest.raises(ValueError): + split_port("localhost:") + + def test_with_no_container_port(self): + with pytest.raises(ValueError): + split_port("localhost:80:") + + def test_split_port_empty_string(self): + with pytest.raises(ValueError): + split_port("") + + def test_split_port_non_string(self): + assert split_port(1243) == (['1243'], None) + + def test_build_port_bindings_with_one_port(self): + port_bindings = build_port_bindings(["127.0.0.1:1000:1000"]) + assert port_bindings["1000"] == [("127.0.0.1", "1000")] + + def test_build_port_bindings_with_matching_internal_ports(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:1000"]) + assert port_bindings["1000"] == [ + ("127.0.0.1", "1000"), ("127.0.0.1", "2000") + ] + + def test_build_port_bindings_with_nonmatching_internal_ports(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) + assert port_bindings["1000"] == [("127.0.0.1", "1000")] + assert port_bindings["2000"] == [("127.0.0.1", "2000")] + + def test_build_port_bindings_with_port_range(self): + port_bindings = build_port_bindings(["127.0.0.1:1000-1001:1000-1001"]) + assert port_bindings["1000"] == [("127.0.0.1", "1000")] + assert port_bindings["1001"] == [("127.0.0.1", "1001")] + + def test_build_port_bindings_with_matching_internal_port_ranges(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000-1001:1000-1001", "127.0.0.1:2000-2001:1000-1001"]) + assert port_bindings["1000"] == [ + ("127.0.0.1", "1000"), ("127.0.0.1", "2000") + ] + assert port_bindings["1001"] == [ + ("127.0.0.1", "1001"), ("127.0.0.1", "2001") + ] + + def test_build_port_bindings_with_nonmatching_internal_port_ranges(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) + assert port_bindings["1000"] == [("127.0.0.1", "1000")] + assert port_bindings["2000"] == [("127.0.0.1", "2000")] diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_proxy.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_proxy.py new file mode 100644 index 000000000..0eb242702 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_proxy.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import unittest +import sys + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.utils.proxy import ProxyConfig + + +HTTP = 'http://test:80' +HTTPS = 'https://test:443' +FTP = 'ftp://user:password@host:23' +NO_PROXY = 'localhost,.localdomain' +CONFIG = ProxyConfig(http=HTTP, https=HTTPS, ftp=FTP, no_proxy=NO_PROXY) +ENV = { + 'http_proxy': HTTP, + 'HTTP_PROXY': HTTP, + 'https_proxy': HTTPS, + 'HTTPS_PROXY': HTTPS, + 'ftp_proxy': FTP, + 'FTP_PROXY': FTP, + 'no_proxy': NO_PROXY, + 'NO_PROXY': NO_PROXY, +} + + +class ProxyConfigTest(unittest.TestCase): + + def test_from_dict(self): + config = ProxyConfig.from_dict({ + 'httpProxy': HTTP, + 'httpsProxy': HTTPS, + 'ftpProxy': FTP, + 'noProxy': NO_PROXY + }) + self.assertEqual(CONFIG.http, config.http) + self.assertEqual(CONFIG.https, config.https) + self.assertEqual(CONFIG.ftp, config.ftp) + self.assertEqual(CONFIG.no_proxy, config.no_proxy) + + def test_new(self): + config = ProxyConfig() + self.assertIsNone(config.http) + self.assertIsNone(config.https) + self.assertIsNone(config.ftp) + self.assertIsNone(config.no_proxy) + + config = ProxyConfig(http='a', https='b', ftp='c', no_proxy='d') + self.assertEqual(config.http, 'a') + self.assertEqual(config.https, 'b') + self.assertEqual(config.ftp, 'c') + self.assertEqual(config.no_proxy, 'd') + + def test_truthiness(self): + assert not ProxyConfig() + assert ProxyConfig(http='non-zero') + assert ProxyConfig(https='non-zero') + assert ProxyConfig(ftp='non-zero') + assert ProxyConfig(no_proxy='non-zero') + + def test_environment(self): + self.assertDictEqual(CONFIG.get_environment(), ENV) + empty = ProxyConfig() + self.assertDictEqual(empty.get_environment(), {}) + + def test_inject_proxy_environment(self): + # Proxy config is non null, env is None. + self.assertSetEqual( + set(CONFIG.inject_proxy_environment(None)), + set('{k}={v}'.format(k=k, v=v) for k, v in ENV.items())) + + # Proxy config is null, env is None. + self.assertIsNone(ProxyConfig().inject_proxy_environment(None), None) + + env = ['FOO=BAR', 'BAR=BAZ'] + + # Proxy config is non null, env is non null + actual = CONFIG.inject_proxy_environment(env) + expected = ['{k}={v}'.format(k=k, v=v) for k, v in ENV.items()] + env + # It's important that the first 8 variables are the ones from the proxy + # config, and the last 2 are the ones from the input environment + self.assertSetEqual(set(actual[:8]), set(expected[:8])) + self.assertSetEqual(set(actual[-2:]), set(expected[-2:])) + + # Proxy is null, and is non null + self.assertListEqual(ProxyConfig().inject_proxy_environment(env), env) diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_utils.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_utils.py new file mode 100644 index 000000000..cc0dc695a --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/test_utils.py @@ -0,0 +1,488 @@ +# -*- coding: utf-8 -*- +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import base64 +import json +import os +import os.path +import shutil +import tempfile +import unittest +import sys + +from ansible.module_utils.six import PY3 + +import pytest + +if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Python 2.6 is not supported') + +from ansible_collections.community.docker.plugins.module_utils._api.api.client import APIClient +from ansible_collections.community.docker.plugins.module_utils._api.constants import IS_WINDOWS_PLATFORM, DEFAULT_DOCKER_API_VERSION +from ansible_collections.community.docker.plugins.module_utils._api.errors import DockerException +from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import ( + convert_filters, convert_volume_binds, + decode_json_header, kwargs_from_env, parse_bytes, + parse_devices, parse_env_file, parse_host, + parse_repository_tag, split_command, format_environment, +) + + +TEST_CERT_DIR = os.path.join( + os.path.dirname(__file__), + 'testdata/certs', +) + + +class KwargsFromEnvTest(unittest.TestCase): + def setUp(self): + self.os_environ = os.environ.copy() + + def tearDown(self): + os.environ = self.os_environ + + def test_kwargs_from_env_empty(self): + os.environ.update(DOCKER_HOST='', + DOCKER_CERT_PATH='') + os.environ.pop('DOCKER_TLS_VERIFY', None) + + kwargs = kwargs_from_env() + assert kwargs.get('base_url') is None + assert kwargs.get('tls') is None + + def test_kwargs_from_env_tls(self): + os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=TEST_CERT_DIR, + DOCKER_TLS_VERIFY='1') + kwargs = kwargs_from_env(assert_hostname=False) + assert 'tcp://192.168.59.103:2376' == kwargs['base_url'] + assert 'ca.pem' in kwargs['tls'].ca_cert + assert 'cert.pem' in kwargs['tls'].cert[0] + assert 'key.pem' in kwargs['tls'].cert[1] + assert kwargs['tls'].assert_hostname is False + assert kwargs['tls'].verify + + parsed_host = parse_host(kwargs['base_url'], IS_WINDOWS_PLATFORM, True) + kwargs['version'] = DEFAULT_DOCKER_API_VERSION + try: + client = APIClient(**kwargs) + assert parsed_host == client.base_url + assert kwargs['tls'].ca_cert == client.verify + assert kwargs['tls'].cert == client.cert + except TypeError as e: + self.fail(e) + + def test_kwargs_from_env_tls_verify_false(self): + os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=TEST_CERT_DIR, + DOCKER_TLS_VERIFY='') + kwargs = kwargs_from_env(assert_hostname=True) + assert 'tcp://192.168.59.103:2376' == kwargs['base_url'] + assert 'ca.pem' in kwargs['tls'].ca_cert + assert 'cert.pem' in kwargs['tls'].cert[0] + assert 'key.pem' in kwargs['tls'].cert[1] + assert kwargs['tls'].assert_hostname is True + assert kwargs['tls'].verify is False + parsed_host = parse_host(kwargs['base_url'], IS_WINDOWS_PLATFORM, True) + kwargs['version'] = DEFAULT_DOCKER_API_VERSION + try: + client = APIClient(**kwargs) + assert parsed_host == client.base_url + assert kwargs['tls'].cert == client.cert + assert not kwargs['tls'].verify + except TypeError as e: + self.fail(e) + + def test_kwargs_from_env_tls_verify_false_no_cert(self): + temp_dir = tempfile.mkdtemp() + cert_dir = os.path.join(temp_dir, '.docker') + shutil.copytree(TEST_CERT_DIR, cert_dir) + + os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', + HOME=temp_dir, + DOCKER_TLS_VERIFY='') + os.environ.pop('DOCKER_CERT_PATH', None) + kwargs = kwargs_from_env(assert_hostname=True) + assert 'tcp://192.168.59.103:2376' == kwargs['base_url'] + + def test_kwargs_from_env_no_cert_path(self): + try: + temp_dir = tempfile.mkdtemp() + cert_dir = os.path.join(temp_dir, '.docker') + shutil.copytree(TEST_CERT_DIR, cert_dir) + + os.environ.update(HOME=temp_dir, + DOCKER_CERT_PATH='', + DOCKER_TLS_VERIFY='1') + + kwargs = kwargs_from_env() + assert kwargs['tls'].verify + assert cert_dir in kwargs['tls'].ca_cert + assert cert_dir in kwargs['tls'].cert[0] + assert cert_dir in kwargs['tls'].cert[1] + finally: + if temp_dir: + shutil.rmtree(temp_dir) + + def test_kwargs_from_env_alternate_env(self): + # Values in os.environ are entirely ignored if an alternate is + # provided + os.environ.update( + DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=TEST_CERT_DIR, + DOCKER_TLS_VERIFY='' + ) + kwargs = kwargs_from_env(environment={ + 'DOCKER_HOST': 'http://docker.gensokyo.jp:2581', + }) + assert 'http://docker.gensokyo.jp:2581' == kwargs['base_url'] + assert 'tls' not in kwargs + + +class ConverVolumeBindsTest(unittest.TestCase): + def test_convert_volume_binds_empty(self): + assert convert_volume_binds({}) == [] + assert convert_volume_binds([]) == [] + + def test_convert_volume_binds_list(self): + data = ['/a:/a:ro', '/b:/c:z'] + assert convert_volume_binds(data) == data + + def test_convert_volume_binds_complete(self): + data = { + '/mnt/vol1': { + 'bind': '/data', + 'mode': 'ro' + } + } + assert convert_volume_binds(data) == ['/mnt/vol1:/data:ro'] + + def test_convert_volume_binds_compact(self): + data = { + '/mnt/vol1': '/data' + } + assert convert_volume_binds(data) == ['/mnt/vol1:/data:rw'] + + def test_convert_volume_binds_no_mode(self): + data = { + '/mnt/vol1': { + 'bind': '/data' + } + } + assert convert_volume_binds(data) == ['/mnt/vol1:/data:rw'] + + def test_convert_volume_binds_unicode_bytes_input(self): + expected = [u'/mnt/지연:/unicode/박:rw'] + + data = { + u'/mnt/지연'.encode('utf-8'): { + 'bind': u'/unicode/박'.encode('utf-8'), + 'mode': u'rw' + } + } + assert convert_volume_binds(data) == expected + + def test_convert_volume_binds_unicode_unicode_input(self): + expected = [u'/mnt/지연:/unicode/박:rw'] + + data = { + u'/mnt/지연': { + 'bind': u'/unicode/박', + 'mode': u'rw' + } + } + assert convert_volume_binds(data) == expected + + +class ParseEnvFileTest(unittest.TestCase): + def generate_tempfile(self, file_content=None): + """ + Generates a temporary file for tests with the content + of 'file_content' and returns the filename. + Don't forget to unlink the file with os.unlink() after. + """ + local_tempfile = tempfile.NamedTemporaryFile(delete=False) + local_tempfile.write(file_content.encode('UTF-8')) + local_tempfile.close() + return local_tempfile.name + + def test_parse_env_file_proper(self): + env_file = self.generate_tempfile( + file_content='USER=jdoe\nPASS=secret') + get_parse_env_file = parse_env_file(env_file) + assert get_parse_env_file == {'USER': 'jdoe', 'PASS': 'secret'} + os.unlink(env_file) + + def test_parse_env_file_with_equals_character(self): + env_file = self.generate_tempfile( + file_content='USER=jdoe\nPASS=sec==ret') + get_parse_env_file = parse_env_file(env_file) + assert get_parse_env_file == {'USER': 'jdoe', 'PASS': 'sec==ret'} + os.unlink(env_file) + + def test_parse_env_file_commented_line(self): + env_file = self.generate_tempfile( + file_content='USER=jdoe\n#PASS=secret') + get_parse_env_file = parse_env_file(env_file) + assert get_parse_env_file == {'USER': 'jdoe'} + os.unlink(env_file) + + def test_parse_env_file_newline(self): + env_file = self.generate_tempfile( + file_content='\nUSER=jdoe\n\n\nPASS=secret') + get_parse_env_file = parse_env_file(env_file) + assert get_parse_env_file == {'USER': 'jdoe', 'PASS': 'secret'} + os.unlink(env_file) + + def test_parse_env_file_invalid_line(self): + env_file = self.generate_tempfile( + file_content='USER jdoe') + with pytest.raises(DockerException): + parse_env_file(env_file) + os.unlink(env_file) + + +class ParseHostTest(unittest.TestCase): + def test_parse_host(self): + invalid_hosts = [ + '0.0.0.0', + 'tcp://', + 'udp://127.0.0.1', + 'udp://127.0.0.1:2375', + 'ssh://:22/path', + 'tcp://netloc:3333/path?q=1', + 'unix:///sock/path#fragment', + 'https://netloc:3333/path;params', + 'ssh://:clearpassword@host:22', + ] + + valid_hosts = { + '0.0.0.1:5555': 'http://0.0.0.1:5555', + ':6666': 'http://127.0.0.1:6666', + 'tcp://:7777': 'http://127.0.0.1:7777', + 'http://:7777': 'http://127.0.0.1:7777', + 'https://kokia.jp:2375': 'https://kokia.jp:2375', + 'unix:///var/run/docker.sock': 'http+unix:///var/run/docker.sock', + 'unix://': 'http+unix:///var/run/docker.sock', + '12.234.45.127:2375/docker/engine': ( + 'http://12.234.45.127:2375/docker/engine' + ), + 'somehost.net:80/service/swarm': ( + 'http://somehost.net:80/service/swarm' + ), + 'npipe:////./pipe/docker_engine': 'npipe:////./pipe/docker_engine', + '[fd12::82d1]:2375': 'http://[fd12::82d1]:2375', + 'https://[fd12:5672::12aa]:1090': 'https://[fd12:5672::12aa]:1090', + '[fd12::82d1]:2375/docker/engine': ( + 'http://[fd12::82d1]:2375/docker/engine' + ), + 'ssh://[fd12::82d1]': 'ssh://[fd12::82d1]:22', + 'ssh://user@[fd12::82d1]:8765': 'ssh://user@[fd12::82d1]:8765', + 'ssh://': 'ssh://127.0.0.1:22', + 'ssh://user@localhost:22': 'ssh://user@localhost:22', + 'ssh://user@remote': 'ssh://user@remote:22', + } + + for host in invalid_hosts: + msg = 'Should have failed to parse invalid host: {0}'.format(host) + with self.assertRaises(DockerException, msg=msg): + parse_host(host, None) + + for host, expected in valid_hosts.items(): + self.assertEqual( + parse_host(host, None), + expected, + msg='Failed to parse valid host: {0}'.format(host), + ) + + def test_parse_host_empty_value(self): + unix_socket = 'http+unix:///var/run/docker.sock' + npipe = 'npipe:////./pipe/docker_engine' + + for val in [None, '']: + assert parse_host(val, is_win32=False) == unix_socket + assert parse_host(val, is_win32=True) == npipe + + def test_parse_host_tls(self): + host_value = 'myhost.docker.net:3348' + expected_result = 'https://myhost.docker.net:3348' + assert parse_host(host_value, tls=True) == expected_result + + def test_parse_host_tls_tcp_proto(self): + host_value = 'tcp://myhost.docker.net:3348' + expected_result = 'https://myhost.docker.net:3348' + assert parse_host(host_value, tls=True) == expected_result + + def test_parse_host_trailing_slash(self): + host_value = 'tcp://myhost.docker.net:2376/' + expected_result = 'http://myhost.docker.net:2376' + assert parse_host(host_value) == expected_result + + +class ParseRepositoryTagTest(unittest.TestCase): + sha = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + + def test_index_image_no_tag(self): + assert parse_repository_tag("root") == ("root", None) + + def test_index_image_tag(self): + assert parse_repository_tag("root:tag") == ("root", "tag") + + def test_index_user_image_no_tag(self): + assert parse_repository_tag("user/repo") == ("user/repo", None) + + def test_index_user_image_tag(self): + assert parse_repository_tag("user/repo:tag") == ("user/repo", "tag") + + def test_private_reg_image_no_tag(self): + assert parse_repository_tag("url:5000/repo") == ("url:5000/repo", None) + + def test_private_reg_image_tag(self): + assert parse_repository_tag("url:5000/repo:tag") == ( + "url:5000/repo", "tag" + ) + + def test_index_image_sha(self): + assert parse_repository_tag("root@sha256:{sha}".format(sha=self.sha)) == ( + "root", "sha256:{sha}".format(sha=self.sha) + ) + + def test_private_reg_image_sha(self): + assert parse_repository_tag( + "url:5000/repo@sha256:{sha}".format(sha=self.sha) + ) == ("url:5000/repo", "sha256:{sha}".format(sha=self.sha)) + + +class ParseDeviceTest(unittest.TestCase): + def test_dict(self): + devices = parse_devices([{ + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'r' + }]) + assert devices[0] == { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'r' + } + + def test_partial_string_definition(self): + devices = parse_devices(['/dev/sda1']) + assert devices[0] == { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/sda1', + 'CgroupPermissions': 'rwm' + } + + def test_permissionless_string_definition(self): + devices = parse_devices(['/dev/sda1:/dev/mnt1']) + assert devices[0] == { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'rwm' + } + + def test_full_string_definition(self): + devices = parse_devices(['/dev/sda1:/dev/mnt1:r']) + assert devices[0] == { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'r' + } + + def test_hybrid_list(self): + devices = parse_devices([ + '/dev/sda1:/dev/mnt1:rw', + { + 'PathOnHost': '/dev/sda2', + 'PathInContainer': '/dev/mnt2', + 'CgroupPermissions': 'r' + } + ]) + + assert devices[0] == { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'rw' + } + assert devices[1] == { + 'PathOnHost': '/dev/sda2', + 'PathInContainer': '/dev/mnt2', + 'CgroupPermissions': 'r' + } + + +class ParseBytesTest(unittest.TestCase): + def test_parse_bytes_valid(self): + assert parse_bytes("512MB") == 536870912 + assert parse_bytes("512M") == 536870912 + assert parse_bytes("512m") == 536870912 + + def test_parse_bytes_invalid(self): + with pytest.raises(DockerException): + parse_bytes("512MK") + with pytest.raises(DockerException): + parse_bytes("512L") + with pytest.raises(DockerException): + parse_bytes("127.0.0.1K") + + def test_parse_bytes_float(self): + assert parse_bytes("1.5k") == 1536 + + +class UtilsTest(unittest.TestCase): + longMessage = True + + def test_convert_filters(self): + tests = [ + ({'dangling': True}, '{"dangling": ["true"]}'), + ({'dangling': "true"}, '{"dangling": ["true"]}'), + ({'exited': 0}, '{"exited": ["0"]}'), + ({'exited': [0, 1]}, '{"exited": ["0", "1"]}'), + ] + + for filters, expected in tests: + assert convert_filters(filters) == expected + + def test_decode_json_header(self): + obj = {'a': 'b', 'c': 1} + data = None + if PY3: + data = base64.urlsafe_b64encode(bytes(json.dumps(obj), 'utf-8')) + else: + data = base64.urlsafe_b64encode(json.dumps(obj)) + decoded_data = decode_json_header(data) + assert obj == decoded_data + + +class SplitCommandTest(unittest.TestCase): + def test_split_command_with_unicode(self): + assert split_command(u'echo μμ') == ['echo', 'μμ'] + + @pytest.mark.skipif(PY3, reason="shlex doesn't support bytes in py3") + def test_split_command_with_bytes(self): + assert split_command('echo μμ') == ['echo', 'μμ'] + + +class FormatEnvironmentTest(unittest.TestCase): + def test_format_env_binary_unicode_value(self): + env_dict = { + 'ARTIST_NAME': b'\xec\x86\xa1\xec\xa7\x80\xec\x9d\x80' + } + assert format_environment(env_dict) == [u'ARTIST_NAME=송지은'] + + def test_format_env_no_value(self): + env_dict = { + 'FOO': None, + 'BAR': '', + } + assert sorted(format_environment(env_dict)) == ['BAR=', 'FOO'] diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/ca.pem b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/ca.pem new file mode 100644 index 000000000..5c7093a38 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/ca.pem @@ -0,0 +1,7 @@ +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/cert.pem b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/cert.pem new file mode 100644 index 000000000..5c7093a38 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/cert.pem @@ -0,0 +1,7 @@ +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/key.pem b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/key.pem new file mode 100644 index 000000000..5c7093a38 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/_api/utils/testdata/certs/key.pem @@ -0,0 +1,7 @@ +# This code is part of the Ansible collection community.docker, but is an independent component. +# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) +# +# Copyright (c) 2016-2022 Docker, Inc. +# +# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) +# SPDX-License-Identifier: Apache-2.0 diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/test__scramble.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/test__scramble.py new file mode 100644 index 000000000..ff0043061 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/test__scramble.py @@ -0,0 +1,28 @@ +# Copyright 2022 Red Hat | Ansible +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.docker.plugins.module_utils._scramble import ( + scramble, + unscramble, +) + + +@pytest.mark.parametrize('plaintext, key, scrambled', [ + (u'', b'0', '=S='), + (u'hello', b'\x00', '=S=aGVsbG8='), + (u'hello', b'\x01', '=S=aWRtbW4='), +]) +def test_scramble_unscramble(plaintext, key, scrambled): + scrambled_ = scramble(plaintext, key) + print('{0!r} == {1!r}'.format(scrambled_, scrambled)) + assert scrambled_ == scrambled + + plaintext_ = unscramble(scrambled, key) + print('{0!r} == {1!r}'.format(plaintext_, plaintext)) + assert plaintext_ == plaintext diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_copy.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_copy.py new file mode 100644 index 000000000..3668573b6 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_copy.py @@ -0,0 +1,77 @@ +# Copyright 2022 Red Hat | Ansible +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.docker.plugins.module_utils.copy import ( + _stream_generator_to_fileobj, +) + + +def _simple_generator(sequence): + for elt in sequence: + yield elt + + +@pytest.mark.parametrize('chunks, read_sizes', [ + ( + [ + (1, b'1'), + (1, b'2'), + (1, b'3'), + (1, b'4'), + ], + [ + 1, + 2, + 3, + ] + ), + ( + [ + (1, b'123'), + (1, b'456'), + (1, b'789'), + ], + [ + 1, + 4, + 2, + 2, + 2, + ] + ), + ( + [ + (10 * 1024 * 1024, b'0'), + (10 * 1024 * 1024, b'1'), + ], + [ + 1024 * 1024 - 5, + 5 * 1024 * 1024 - 3, + 10 * 1024 * 1024 - 2, + 2 * 1024 * 1024 - 1, + 2 * 1024 * 1024 + 5 + 3 + 2 + 1, + ] + ), +]) +def test__stream_generator_to_fileobj(chunks, read_sizes): + chunks = [count * data for count, data in chunks] + stream = _simple_generator(chunks) + expected = b''.join(chunks) + + buffer = b'' + totally_read = 0 + f = _stream_generator_to_fileobj(stream) + for read_size in read_sizes: + chunk = f.read(read_size) + assert len(chunk) == min(read_size, len(expected) - len(buffer)) + buffer += chunk + totally_read += read_size + + assert buffer == expected[:len(buffer)] + assert min(totally_read, len(expected)) == len(buffer) diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_image_archive.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_image_archive.py new file mode 100644 index 000000000..10573b96a --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_image_archive.py @@ -0,0 +1,94 @@ +# Copyright 2022 Red Hat | Ansible +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +import tarfile + +from ansible_collections.community.docker.plugins.module_utils.image_archive import ( + api_image_id, + archived_image_manifest, + ImageArchiveInvalidException +) + +from ..test_support.docker_image_archive_stubbing import ( + write_imitation_archive, + write_imitation_archive_with_manifest, + write_irrelevant_tar, +) + + +@pytest.fixture +def tar_file_name(tmpdir): + ''' + Return the name of a non-existing tar file in an existing temporary directory. + ''' + + # Cast to str required by Python 2.x + return str(tmpdir.join('foo.tar')) + + +@pytest.mark.parametrize('expected, value', [ + ('sha256:foo', 'foo'), + ('sha256:bar', 'bar') +]) +def test_api_image_id_from_archive_id(expected, value): + assert api_image_id(value) == expected + + +def test_archived_image_manifest_extracts(tar_file_name): + expected_id = "abcde12345" + expected_tags = ["foo:latest", "bar:v1"] + + write_imitation_archive(tar_file_name, expected_id, expected_tags) + + actual = archived_image_manifest(tar_file_name) + + assert actual.image_id == expected_id + assert actual.repo_tags == expected_tags + + +def test_archived_image_manifest_extracts_nothing_when_file_not_present(tar_file_name): + image_id = archived_image_manifest(tar_file_name) + + assert image_id is None + + +def test_archived_image_manifest_raises_when_file_not_a_tar(): + try: + archived_image_manifest(__file__) + raise AssertionError() + except ImageArchiveInvalidException as e: + assert isinstance(e.cause, tarfile.ReadError) + assert str(__file__) in str(e) + + +def test_archived_image_manifest_raises_when_tar_missing_manifest(tar_file_name): + write_irrelevant_tar(tar_file_name) + + try: + archived_image_manifest(tar_file_name) + raise AssertionError() + except ImageArchiveInvalidException as e: + assert isinstance(e.cause, KeyError) + assert 'manifest.json' in str(e.cause) + + +def test_archived_image_manifest_raises_when_manifest_missing_id(tar_file_name): + manifest = [ + { + 'foo': 'bar' + } + ] + + write_imitation_archive_with_manifest(tar_file_name, manifest) + + try: + archived_image_manifest(tar_file_name) + raise AssertionError() + except ImageArchiveInvalidException as e: + assert isinstance(e.cause, KeyError) + assert 'Config' in str(e.cause) diff --git a/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_util.py b/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_util.py new file mode 100644 index 000000000..c7d36212f --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/module_utils/test_util.py @@ -0,0 +1,522 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.docker.plugins.module_utils.util import ( + compare_dict_allow_more_present, + compare_generic, + convert_duration_to_nanosecond, + parse_healthcheck +) + +DICT_ALLOW_MORE_PRESENT = ( + { + 'av': {}, + 'bv': {'a': 1}, + 'result': True + }, + { + 'av': {'a': 1}, + 'bv': {'a': 1, 'b': 2}, + 'result': True + }, + { + 'av': {'a': 1}, + 'bv': {'b': 2}, + 'result': False + }, + { + 'av': {'a': 1}, + 'bv': {'a': None, 'b': 1}, + 'result': False + }, + { + 'av': {'a': None}, + 'bv': {'b': 1}, + 'result': False + }, +) + +COMPARE_GENERIC = [ + ######################################################################################## + # value + { + 'a': 1, + 'b': 2, + 'method': 'strict', + 'type': 'value', + 'result': False + }, + { + 'a': 'hello', + 'b': 'hello', + 'method': 'strict', + 'type': 'value', + 'result': True + }, + { + 'a': None, + 'b': 'hello', + 'method': 'strict', + 'type': 'value', + 'result': False + }, + { + 'a': None, + 'b': None, + 'method': 'strict', + 'type': 'value', + 'result': True + }, + { + 'a': 1, + 'b': 2, + 'method': 'ignore', + 'type': 'value', + 'result': True + }, + { + 'a': None, + 'b': 2, + 'method': 'ignore', + 'type': 'value', + 'result': True + }, + ######################################################################################## + # list + { + 'a': [ + 'x', + ], + 'b': [ + 'y', + ], + 'method': 'strict', + 'type': 'list', + 'result': False + }, + { + 'a': [ + 'x', + ], + 'b': [ + 'x', + 'x', + ], + 'method': 'strict', + 'type': 'list', + 'result': False + }, + { + 'a': [ + 'x', + 'y', + ], + 'b': [ + 'x', + 'y', + ], + 'method': 'strict', + 'type': 'list', + 'result': True + }, + { + 'a': [ + 'x', + 'y', + ], + 'b': [ + 'y', + 'x', + ], + 'method': 'strict', + 'type': 'list', + 'result': False + }, + { + 'a': [ + 'x', + 'y', + ], + 'b': [ + 'x', + ], + 'method': 'allow_more_present', + 'type': 'list', + 'result': False + }, + { + 'a': [ + 'x', + ], + 'b': [ + 'x', + 'y', + ], + 'method': 'allow_more_present', + 'type': 'list', + 'result': True + }, + { + 'a': [ + 'x', + 'x', + 'y', + ], + 'b': [ + 'x', + 'y', + ], + 'method': 'allow_more_present', + 'type': 'list', + 'result': False + }, + { + 'a': [ + 'x', + 'z', + ], + 'b': [ + 'x', + 'y', + 'x', + 'z', + ], + 'method': 'allow_more_present', + 'type': 'list', + 'result': True + }, + { + 'a': [ + 'x', + 'y', + ], + 'b': [ + 'y', + 'x', + ], + 'method': 'ignore', + 'type': 'list', + 'result': True + }, + ######################################################################################## + # set + { + 'a': [ + 'x', + ], + 'b': [ + 'y', + ], + 'method': 'strict', + 'type': 'set', + 'result': False + }, + { + 'a': [ + 'x', + ], + 'b': [ + 'x', + 'x', + ], + 'method': 'strict', + 'type': 'set', + 'result': True + }, + { + 'a': [ + 'x', + 'y', + ], + 'b': [ + 'x', + 'y', + ], + 'method': 'strict', + 'type': 'set', + 'result': True + }, + { + 'a': [ + 'x', + 'y', + ], + 'b': [ + 'y', + 'x', + ], + 'method': 'strict', + 'type': 'set', + 'result': True + }, + { + 'a': [ + 'x', + 'y', + ], + 'b': [ + 'x', + ], + 'method': 'allow_more_present', + 'type': 'set', + 'result': False + }, + { + 'a': [ + 'x', + ], + 'b': [ + 'x', + 'y', + ], + 'method': 'allow_more_present', + 'type': 'set', + 'result': True + }, + { + 'a': [ + 'x', + 'x', + 'y', + ], + 'b': [ + 'x', + 'y', + ], + 'method': 'allow_more_present', + 'type': 'set', + 'result': True + }, + { + 'a': [ + 'x', + 'z', + ], + 'b': [ + 'x', + 'y', + 'x', + 'z', + ], + 'method': 'allow_more_present', + 'type': 'set', + 'result': True + }, + { + 'a': [ + 'x', + 'a', + ], + 'b': [ + 'y', + 'z', + ], + 'method': 'ignore', + 'type': 'set', + 'result': True + }, + ######################################################################################## + # set(dict) + { + 'a': [ + {'x': 1}, + ], + 'b': [ + {'y': 1}, + ], + 'method': 'strict', + 'type': 'set(dict)', + 'result': False + }, + { + 'a': [ + {'x': 1}, + ], + 'b': [ + {'x': 1}, + ], + 'method': 'strict', + 'type': 'set(dict)', + 'result': True + }, + { + 'a': [ + {'x': 1}, + ], + 'b': [ + {'x': 1, 'y': 2}, + ], + 'method': 'strict', + 'type': 'set(dict)', + 'result': True + }, + { + 'a': [ + {'x': 1}, + {'x': 2, 'y': 3}, + ], + 'b': [ + {'x': 1}, + {'x': 2, 'y': 3}, + ], + 'method': 'strict', + 'type': 'set(dict)', + 'result': True + }, + { + 'a': [ + {'x': 1}, + ], + 'b': [ + {'x': 1, 'z': 2}, + {'x': 2, 'y': 3}, + ], + 'method': 'allow_more_present', + 'type': 'set(dict)', + 'result': True + }, + { + 'a': [ + {'x': 1, 'y': 2}, + ], + 'b': [ + {'x': 1}, + {'x': 2, 'y': 3}, + ], + 'method': 'allow_more_present', + 'type': 'set(dict)', + 'result': False + }, + { + 'a': [ + {'x': 1, 'y': 3}, + ], + 'b': [ + {'x': 1}, + {'x': 1, 'y': 3, 'z': 4}, + ], + 'method': 'allow_more_present', + 'type': 'set(dict)', + 'result': True + }, + { + 'a': [ + {'x': 1}, + {'x': 2, 'y': 3}, + ], + 'b': [ + {'x': 1}, + ], + 'method': 'ignore', + 'type': 'set(dict)', + 'result': True + }, + ######################################################################################## + # dict + { + 'a': {'x': 1}, + 'b': {'y': 1}, + 'method': 'strict', + 'type': 'dict', + 'result': False + }, + { + 'a': {'x': 1}, + 'b': {'x': 1, 'y': 2}, + 'method': 'strict', + 'type': 'dict', + 'result': False + }, + { + 'a': {'x': 1}, + 'b': {'x': 1}, + 'method': 'strict', + 'type': 'dict', + 'result': True + }, + { + 'a': {'x': 1, 'z': 2}, + 'b': {'x': 1, 'y': 2}, + 'method': 'strict', + 'type': 'dict', + 'result': False + }, + { + 'a': {'x': 1, 'z': 2}, + 'b': {'x': 1, 'y': 2}, + 'method': 'ignore', + 'type': 'dict', + 'result': True + }, +] + [{ + 'a': entry['av'], + 'b': entry['bv'], + 'method': 'allow_more_present', + 'type': 'dict', + 'result': entry['result'] +} for entry in DICT_ALLOW_MORE_PRESENT] + + +@pytest.mark.parametrize("entry", DICT_ALLOW_MORE_PRESENT) +def test_dict_allow_more_present(entry): + assert compare_dict_allow_more_present(entry['av'], entry['bv']) == entry['result'] + + +@pytest.mark.parametrize("entry", COMPARE_GENERIC) +def test_compare_generic(entry): + assert compare_generic(entry['a'], entry['b'], entry['method'], entry['type']) == entry['result'] + + +def test_convert_duration_to_nanosecond(): + nanoseconds = convert_duration_to_nanosecond('5s') + assert nanoseconds == 5000000000 + nanoseconds = convert_duration_to_nanosecond('1m5s') + assert nanoseconds == 65000000000 + with pytest.raises(ValueError): + convert_duration_to_nanosecond([1, 2, 3]) + with pytest.raises(ValueError): + convert_duration_to_nanosecond('10x') + + +def test_parse_healthcheck(): + result, disabled = parse_healthcheck({ + 'test': 'sleep 1', + 'interval': '1s', + }) + assert disabled is False + assert result == { + 'test': ['CMD-SHELL', 'sleep 1'], + 'interval': 1000000000 + } + + result, disabled = parse_healthcheck({ + 'test': ['NONE'], + }) + assert result is None + assert disabled + + result, disabled = parse_healthcheck({ + 'test': 'sleep 1', + 'interval': '1s423ms' + }) + assert result == { + 'test': ['CMD-SHELL', 'sleep 1'], + 'interval': 1423000000 + } + assert disabled is False + + result, disabled = parse_healthcheck({ + 'test': 'sleep 1', + 'interval': '1h1m2s3ms4us' + }) + assert result == { + 'test': ['CMD-SHELL', 'sleep 1'], + 'interval': 3662003004000 + } + assert disabled is False diff --git a/ansible_collections/community/docker/tests/unit/plugins/modules/conftest.py b/ansible_collections/community/docker/tests/unit/plugins/modules/conftest.py new file mode 100644 index 000000000..0ed3dd447 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/modules/conftest.py @@ -0,0 +1,32 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +import pytest + +from ansible.module_utils.six import string_types +from ansible.module_utils.common.text.converters import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping + + +@pytest.fixture +def patch_ansible_module(request, mocker): + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if 'ANSIBLE_MODULE_ARGS' not in request.param: + request.param = {'ANSIBLE_MODULE_ARGS': request.param} + if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False + args = json.dumps(request.param) + else: + raise Exception('Malformed data to the patch_ansible_module pytest fixture') + + mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args)) diff --git a/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_image.py b/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_image.py new file mode 100644 index 000000000..3401837f2 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_image.py @@ -0,0 +1,114 @@ +# Copyright 2022 Red Hat | Ansible +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.docker.plugins.modules.docker_image import ImageManager + +from ansible_collections.community.docker.plugins.module_utils.image_archive import api_image_id + +from ..test_support.docker_image_archive_stubbing import ( + write_imitation_archive, + write_irrelevant_tar, +) + + +def assert_no_logging(msg): + raise AssertionError('Should not have logged anything but logged %s' % msg) + + +def capture_logging(messages): + def capture(msg): + messages.append(msg) + + return capture + + +@pytest.fixture +def tar_file_name(tmpdir): + """ + Return the name of a non-existing tar file in an existing temporary directory. + """ + + # Cast to str required by Python 2.x + return str(tmpdir.join('foo.tar')) + + +def test_archived_image_action_when_missing(tar_file_name): + fake_name = 'a:latest' + fake_id = 'a1' + + expected = 'Archived image %s to %s, since none present' % (fake_name, tar_file_name) + + actual = ImageManager.archived_image_action(assert_no_logging, tar_file_name, fake_name, api_image_id(fake_id)) + + assert actual == expected + + +def test_archived_image_action_when_current(tar_file_name): + fake_name = 'b:latest' + fake_id = 'b2' + + write_imitation_archive(tar_file_name, fake_id, [fake_name]) + + actual = ImageManager.archived_image_action(assert_no_logging, tar_file_name, fake_name, api_image_id(fake_id)) + + assert actual is None + + +def test_archived_image_action_when_invalid(tar_file_name): + fake_name = 'c:1.2.3' + fake_id = 'c3' + + write_irrelevant_tar(tar_file_name) + + expected = 'Archived image %s to %s, overwriting an unreadable archive file' % (fake_name, tar_file_name) + + actual_log = [] + actual = ImageManager.archived_image_action( + capture_logging(actual_log), + tar_file_name, + fake_name, + api_image_id(fake_id) + ) + + assert actual == expected + + assert len(actual_log) == 1 + assert actual_log[0].startswith('Unable to extract manifest summary from archive') + + +def test_archived_image_action_when_obsolete_by_id(tar_file_name): + fake_name = 'd:0.0.1' + old_id = 'e5' + new_id = 'd4' + + write_imitation_archive(tar_file_name, old_id, [fake_name]) + + expected = 'Archived image %s to %s, overwriting archive with image %s named %s' % ( + fake_name, tar_file_name, old_id, fake_name + ) + actual = ImageManager.archived_image_action(assert_no_logging, tar_file_name, fake_name, api_image_id(new_id)) + + assert actual == expected + + +def test_archived_image_action_when_obsolete_by_name(tar_file_name): + old_name = 'hi' + new_name = 'd:0.0.1' + fake_id = 'd4' + + write_imitation_archive(tar_file_name, fake_id, [old_name]) + + expected = 'Archived image %s to %s, overwriting archive with image %s named %s' % ( + new_name, tar_file_name, fake_id, old_name + ) + actual = ImageManager.archived_image_action(assert_no_logging, tar_file_name, new_name, api_image_id(fake_id)) + + print('actual : %s', actual) + print('expected : %s', expected) + assert actual == expected diff --git a/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_network.py b/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_network.py new file mode 100644 index 000000000..a937c6db4 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_network.py @@ -0,0 +1,35 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +"""Unit tests for docker_network.""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.docker.plugins.modules.docker_network import validate_cidr + + +@pytest.mark.parametrize("cidr,expected", [ + ('192.168.0.1/16', 'ipv4'), + ('192.168.0.1/24', 'ipv4'), + ('192.168.0.1/32', 'ipv4'), + ('fdd1:ac8c:0557:7ce2::/64', 'ipv6'), + ('fdd1:ac8c:0557:7ce2::/128', 'ipv6'), +]) +def test_validate_cidr_positives(cidr, expected): + assert validate_cidr(cidr) == expected + + +@pytest.mark.parametrize("cidr", [ + '192.168.0.1', + '192.168.0.1/34', + '192.168.0.1/asd', + 'fdd1:ac8c:0557:7ce2::', +]) +def test_validate_cidr_negatives(cidr): + with pytest.raises(ValueError) as e: + validate_cidr(cidr) + assert '"{0}" is not a valid CIDR'.format(cidr) == str(e.value) diff --git a/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_swarm_service.py b/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_swarm_service.py new file mode 100644 index 000000000..1cef623bf --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/modules/test_docker_swarm_service.py @@ -0,0 +1,514 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + + +class APIErrorMock(Exception): + def __init__(self, message, response=None, explanation=None): + self.message = message + self.response = response + self.explanation = explanation + + +@pytest.fixture(autouse=True) +def docker_module_mock(mocker): + docker_module_mock = mocker.MagicMock() + docker_utils_module_mock = mocker.MagicMock() + docker_errors_module_mock = mocker.MagicMock() + docker_errors_module_mock.APIError = APIErrorMock + mock_modules = { + 'docker': docker_module_mock, + 'docker.utils': docker_utils_module_mock, + 'docker.errors': docker_errors_module_mock, + } + return mocker.patch.dict('sys.modules', **mock_modules) + + +@pytest.fixture(autouse=True) +def docker_swarm_service(): + from ansible_collections.community.docker.plugins.modules import docker_swarm_service + + return docker_swarm_service + + +def test_retry_on_out_of_sequence_error(mocker, docker_swarm_service): + run_mock = mocker.MagicMock( + side_effect=APIErrorMock( + message='', + response=None, + explanation='rpc error: code = Unknown desc = update out of sequence', + ) + ) + manager = docker_swarm_service.DockerServiceManager(client=None) + manager.run = run_mock + with pytest.raises(APIErrorMock): + manager.run_safe() + assert run_mock.call_count == 3 + + +def test_no_retry_on_general_api_error(mocker, docker_swarm_service): + run_mock = mocker.MagicMock( + side_effect=APIErrorMock(message='', response=None, explanation='some error') + ) + manager = docker_swarm_service.DockerServiceManager(client=None) + manager.run = run_mock + with pytest.raises(APIErrorMock): + manager.run_safe() + assert run_mock.call_count == 1 + + +def test_get_docker_environment(mocker, docker_swarm_service): + env_file_result = {'TEST1': 'A', 'TEST2': 'B', 'TEST3': 'C'} + env_dict = {'TEST3': 'CC', 'TEST4': 'D'} + env_string = "TEST3=CC,TEST4=D" + + env_list = ['TEST3=CC', 'TEST4=D'] + expected_result = sorted(['TEST1=A', 'TEST2=B', 'TEST3=CC', 'TEST4=D']) + mocker.patch.object( + docker_swarm_service, 'parse_env_file', return_value=env_file_result + ) + mocker.patch.object( + docker_swarm_service, + 'format_environment', + side_effect=lambda d: ['{0}={1}'.format(key, value) for key, value in d.items()], + ) + # Test with env dict and file + result = docker_swarm_service.get_docker_environment( + env_dict, env_files=['dummypath'] + ) + assert result == expected_result + # Test with env list and file + result = docker_swarm_service.get_docker_environment( + env_list, + env_files=['dummypath'] + ) + assert result == expected_result + # Test with env string and file + result = docker_swarm_service.get_docker_environment( + env_string, env_files=['dummypath'] + ) + assert result == expected_result + + assert result == expected_result + # Test with empty env + result = docker_swarm_service.get_docker_environment( + [], env_files=None + ) + assert result == [] + # Test with empty env_files + result = docker_swarm_service.get_docker_environment( + None, env_files=[] + ) + assert result == [] + + +def test_get_nanoseconds_from_raw_option(docker_swarm_service): + value = docker_swarm_service.get_nanoseconds_from_raw_option('test', None) + assert value is None + + value = docker_swarm_service.get_nanoseconds_from_raw_option('test', '1m30s535ms') + assert value == 90535000000 + + value = docker_swarm_service.get_nanoseconds_from_raw_option('test', 10000000000) + assert value == 10000000000 + + with pytest.raises(ValueError): + docker_swarm_service.get_nanoseconds_from_raw_option('test', []) + + +def test_has_dict_changed(docker_swarm_service): + assert not docker_swarm_service.has_dict_changed( + {"a": 1}, + {"a": 1}, + ) + assert not docker_swarm_service.has_dict_changed( + {"a": 1}, + {"a": 1, "b": 2} + ) + assert docker_swarm_service.has_dict_changed( + {"a": 1}, + {"a": 2, "b": 2} + ) + assert docker_swarm_service.has_dict_changed( + {"a": 1, "b": 1}, + {"a": 1} + ) + assert not docker_swarm_service.has_dict_changed( + None, + {"a": 2, "b": 2} + ) + assert docker_swarm_service.has_dict_changed( + {}, + {"a": 2, "b": 2} + ) + assert docker_swarm_service.has_dict_changed( + {"a": 1}, + {} + ) + assert docker_swarm_service.has_dict_changed( + {"a": 1}, + None + ) + assert not docker_swarm_service.has_dict_changed( + {}, + {} + ) + assert not docker_swarm_service.has_dict_changed( + None, + None + ) + assert not docker_swarm_service.has_dict_changed( + {}, + None + ) + assert not docker_swarm_service.has_dict_changed( + None, + {} + ) + + +def test_has_list_changed(docker_swarm_service): + + # List comparisons without dictionaries + # I could improve the indenting, but pycodestyle wants this instead + assert not docker_swarm_service.has_list_changed(None, None) + assert not docker_swarm_service.has_list_changed(None, []) + assert not docker_swarm_service.has_list_changed(None, [1, 2]) + + assert not docker_swarm_service.has_list_changed([], None) + assert not docker_swarm_service.has_list_changed([], []) + assert docker_swarm_service.has_list_changed([], [1, 2]) + + assert docker_swarm_service.has_list_changed([1, 2], None) + assert docker_swarm_service.has_list_changed([1, 2], []) + + assert docker_swarm_service.has_list_changed([1, 2, 3], [1, 2]) + assert docker_swarm_service.has_list_changed([1, 2], [1, 2, 3]) + + # Check list sorting + assert not docker_swarm_service.has_list_changed([1, 2], [2, 1]) + assert docker_swarm_service.has_list_changed( + [1, 2], + [2, 1], + sort_lists=False + ) + + # Check type matching + assert docker_swarm_service.has_list_changed([None, 1], [2, 1]) + assert docker_swarm_service.has_list_changed([2, 1], [None, 1]) + assert docker_swarm_service.has_list_changed( + "command --with args", + ['command', '--with', 'args'] + ) + assert docker_swarm_service.has_list_changed( + ['sleep', '3400'], + [u'sleep', u'3600'], + sort_lists=False + ) + + # List comparisons with dictionaries + assert not docker_swarm_service.has_list_changed( + [{'a': 1}], + [{'a': 1}], + sort_key='a' + ) + + assert not docker_swarm_service.has_list_changed( + [{'a': 1}, {'a': 2}], + [{'a': 1}, {'a': 2}], + sort_key='a' + ) + + with pytest.raises(Exception): + docker_swarm_service.has_list_changed( + [{'a': 1}, {'a': 2}], + [{'a': 1}, {'a': 2}] + ) + + # List sort checking with sort key + assert not docker_swarm_service.has_list_changed( + [{'a': 1}, {'a': 2}], + [{'a': 2}, {'a': 1}], + sort_key='a' + ) + assert docker_swarm_service.has_list_changed( + [{'a': 1}, {'a': 2}], + [{'a': 2}, {'a': 1}], + sort_lists=False + ) + + assert docker_swarm_service.has_list_changed( + [{'a': 1}, {'a': 2}, {'a': 3}], + [{'a': 2}, {'a': 1}], + sort_key='a' + ) + assert docker_swarm_service.has_list_changed( + [{'a': 1}, {'a': 2}], + [{'a': 1}, {'a': 2}, {'a': 3}], + sort_lists=False + ) + + # Additional dictionary elements + assert not docker_swarm_service.has_list_changed( + [ + {"src": 1, "dst": 2}, + {"src": 1, "dst": 2, "protocol": "udp"}, + ], + [ + {"src": 1, "dst": 2, "protocol": "tcp"}, + {"src": 1, "dst": 2, "protocol": "udp"}, + ], + sort_key='dst' + ) + assert not docker_swarm_service.has_list_changed( + [ + {"src": 1, "dst": 2, "protocol": "udp"}, + {"src": 1, "dst": 3, "protocol": "tcp"}, + ], + [ + {"src": 1, "dst": 2, "protocol": "udp"}, + {"src": 1, "dst": 3, "protocol": "tcp"}, + ], + sort_key='dst' + ) + assert docker_swarm_service.has_list_changed( + [ + {"src": 1, "dst": 2, "protocol": "udp"}, + {"src": 1, "dst": 2}, + {"src": 3, "dst": 4}, + ], + [ + {"src": 1, "dst": 3, "protocol": "udp"}, + {"src": 1, "dst": 2, "protocol": "tcp"}, + {"src": 3, "dst": 4, "protocol": "tcp"}, + ], + sort_key='dst' + ) + assert docker_swarm_service.has_list_changed( + [ + {"src": 1, "dst": 3, "protocol": "tcp"}, + {"src": 1, "dst": 2, "protocol": "udp"}, + ], + [ + {"src": 1, "dst": 2, "protocol": "tcp"}, + {"src": 1, "dst": 2, "protocol": "udp"}, + ], + sort_key='dst' + ) + assert docker_swarm_service.has_list_changed( + [ + {"src": 1, "dst": 2, "protocol": "udp"}, + {"src": 1, "dst": 2, "protocol": "tcp", "extra": {"test": "foo"}}, + ], + [ + {"src": 1, "dst": 2, "protocol": "udp"}, + {"src": 1, "dst": 2, "protocol": "tcp"}, + ], + sort_key='dst' + ) + assert not docker_swarm_service.has_list_changed( + [{'id': '123', 'aliases': []}], + [{'id': '123'}], + sort_key='id' + ) + + +def test_have_networks_changed(docker_swarm_service): + assert not docker_swarm_service.have_networks_changed( + None, + None + ) + + assert not docker_swarm_service.have_networks_changed( + [], + None + ) + + assert not docker_swarm_service.have_networks_changed( + [{'id': 1}], + [{'id': 1}] + ) + + assert docker_swarm_service.have_networks_changed( + [{'id': 1}], + [{'id': 1}, {'id': 2}] + ) + + assert not docker_swarm_service.have_networks_changed( + [{'id': 1}, {'id': 2}], + [{'id': 1}, {'id': 2}] + ) + + assert not docker_swarm_service.have_networks_changed( + [{'id': 1}, {'id': 2}], + [{'id': 2}, {'id': 1}] + ) + + assert not docker_swarm_service.have_networks_changed( + [ + {'id': 1}, + {'id': 2, 'aliases': []} + ], + [ + {'id': 1}, + {'id': 2} + ] + ) + + assert docker_swarm_service.have_networks_changed( + [ + {'id': 1}, + {'id': 2, 'aliases': ['alias1']} + ], + [ + {'id': 1}, + {'id': 2} + ] + ) + + assert docker_swarm_service.have_networks_changed( + [ + {'id': 1}, + {'id': 2, 'aliases': ['alias1', 'alias2']} + ], + [ + {'id': 1}, + {'id': 2, 'aliases': ['alias1']} + ] + ) + + assert not docker_swarm_service.have_networks_changed( + [ + {'id': 1}, + {'id': 2, 'aliases': ['alias1', 'alias2']} + ], + [ + {'id': 1}, + {'id': 2, 'aliases': ['alias1', 'alias2']} + ] + ) + + assert not docker_swarm_service.have_networks_changed( + [ + {'id': 1}, + {'id': 2, 'aliases': ['alias1', 'alias2']} + ], + [ + {'id': 1}, + {'id': 2, 'aliases': ['alias2', 'alias1']} + ] + ) + + assert not docker_swarm_service.have_networks_changed( + [ + {'id': 1, 'options': {}}, + {'id': 2, 'aliases': ['alias1', 'alias2']}], + [ + {'id': 1}, + {'id': 2, 'aliases': ['alias2', 'alias1']} + ] + ) + + assert not docker_swarm_service.have_networks_changed( + [ + {'id': 1, 'options': {'option1': 'value1'}}, + {'id': 2, 'aliases': ['alias1', 'alias2']}], + [ + {'id': 1, 'options': {'option1': 'value1'}}, + {'id': 2, 'aliases': ['alias2', 'alias1']} + ] + ) + + assert docker_swarm_service.have_networks_changed( + [ + {'id': 1, 'options': {'option1': 'value1'}}, + {'id': 2, 'aliases': ['alias1', 'alias2']}], + [ + {'id': 1, 'options': {'option1': 'value2'}}, + {'id': 2, 'aliases': ['alias2', 'alias1']} + ] + ) + + +def test_get_docker_networks(docker_swarm_service): + network_names = [ + 'network_1', + 'network_2', + 'network_3', + 'network_4', + ] + networks = [ + network_names[0], + {'name': network_names[1]}, + {'name': network_names[2], 'aliases': ['networkalias1']}, + {'name': network_names[3], 'aliases': ['networkalias2'], 'options': {'foo': 'bar'}}, + ] + network_ids = { + network_names[0]: '1', + network_names[1]: '2', + network_names[2]: '3', + network_names[3]: '4', + } + parsed_networks = docker_swarm_service.get_docker_networks( + networks, + network_ids + ) + assert len(parsed_networks) == 4 + for i, network in enumerate(parsed_networks): + assert 'name' not in network + assert 'id' in network + expected_name = network_names[i] + assert network['id'] == network_ids[expected_name] + if i == 2: + assert network['aliases'] == ['networkalias1'] + if i == 3: + assert network['aliases'] == ['networkalias2'] + if i == 3: + assert 'foo' in network['options'] + # Test missing name + with pytest.raises(TypeError): + docker_swarm_service.get_docker_networks([{'invalid': 'err'}], {'err': 1}) + # test for invalid aliases type + with pytest.raises(TypeError): + docker_swarm_service.get_docker_networks( + [{'name': 'test', 'aliases': 1}], + {'test': 1} + ) + # Test invalid aliases elements + with pytest.raises(TypeError): + docker_swarm_service.get_docker_networks( + [{'name': 'test', 'aliases': [1]}], + {'test': 1} + ) + # Test for invalid options type + with pytest.raises(TypeError): + docker_swarm_service.get_docker_networks( + [{'name': 'test', 'options': 1}], + {'test': 1} + ) + # Test for invalid networks type + with pytest.raises(TypeError): + docker_swarm_service.get_docker_networks( + 1, + {'test': 1} + ) + # Test for non existing networks + with pytest.raises(ValueError): + docker_swarm_service.get_docker_networks( + [{'name': 'idontexist'}], + {'test': 1} + ) + # Test empty values + assert docker_swarm_service.get_docker_networks([], {}) == [] + assert docker_swarm_service.get_docker_networks(None, {}) is None + # Test invalid options + with pytest.raises(TypeError): + docker_swarm_service.get_docker_networks( + [{'name': 'test', 'nonexisting_option': 'foo'}], + {'test': '1'} + ) diff --git a/ansible_collections/community/docker/tests/unit/plugins/test_support/docker_image_archive_stubbing.py b/ansible_collections/community/docker/tests/unit/plugins/test_support/docker_image_archive_stubbing.py new file mode 100644 index 000000000..842ec4cf0 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/plugins/test_support/docker_image_archive_stubbing.py @@ -0,0 +1,76 @@ +# Copyright 2022 Red Hat | Ansible +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import tarfile +from tempfile import TemporaryFile + + +def write_imitation_archive(file_name, image_id, repo_tags): + ''' + Write a tar file meeting these requirements: + + * Has a file manifest.json + * manifest.json contains a one-element array + * The element has a Config property with "[image_id].json" as the value name + + :param file_name: Name of file to create + :type file_name: str + :param image_id: Fake sha256 hash (without the sha256: prefix) + :type image_id: str + :param repo_tags: list of fake image:tag's + :type repo_tags: list + ''' + + manifest = [ + { + 'Config': '%s.json' % image_id, + 'RepoTags': repo_tags + } + ] + + write_imitation_archive_with_manifest(file_name, manifest) + + +def write_imitation_archive_with_manifest(file_name, manifest): + tf = tarfile.open(file_name, 'w') + try: + with TemporaryFile() as f: + f.write(json.dumps(manifest).encode('utf-8')) + + ti = tarfile.TarInfo('manifest.json') + ti.size = f.tell() + + f.seek(0) + tf.addfile(ti, f) + + finally: + # In Python 2.6, this does not have __exit__ + tf.close() + + +def write_irrelevant_tar(file_name): + ''' + Create a tar file that does not match the spec for "docker image save" / "docker image load" commands. + + :param file_name: Name of tar file to create + :type file_name: str + ''' + + tf = tarfile.open(file_name, 'w') + try: + with TemporaryFile() as f: + f.write('Hello, world.'.encode('utf-8')) + + ti = tarfile.TarInfo('hi.txt') + ti.size = f.tell() + + f.seek(0) + tf.addfile(ti, f) + + finally: + tf.close() diff --git a/ansible_collections/community/docker/tests/unit/requirements.txt b/ansible_collections/community/docker/tests/unit/requirements.txt new file mode 100644 index 000000000..386c97e22 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/requirements.txt @@ -0,0 +1,9 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unittest2 ; python_version < '2.7' +importlib ; python_version < '2.7' + +requests +backports.ssl-match-hostname ; python_version < '3.5' diff --git a/ansible_collections/community/docker/tests/unit/requirements.yml b/ansible_collections/community/docker/tests/unit/requirements.yml new file mode 100644 index 000000000..586a6a1b3 --- /dev/null +++ b/ansible_collections/community/docker/tests/unit/requirements.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +collections: +- community.internal_test_tools diff --git a/ansible_collections/community/docker/tests/utils/constraints.txt b/ansible_collections/community/docker/tests/utils/constraints.txt new file mode 100644 index 000000000..d8fcb61d5 --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/constraints.txt @@ -0,0 +1,25 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +bcrypt < 3.2.0 ; python_version <= '3.6' +certifi < 2022.5.18 ; python_version < '3.5' # certifi 2022.5.18 requires Python 3.5 or later +cffi >= 1.14.2, != 1.14.3 # Yanked version which older versions of pip will still install +coverage >= 4.2, < 5.0.0, != 4.3.2 ; python_version <= '3.7' # features in 4.2+ required, avoid known bug in 4.3.2 on python 2.6, coverage 5.0+ incompatible +coverage >= 4.5.4, < 5.0.0 ; python_version > '3.7' # coverage had a bug in < 4.5.4 that would cause unit tests to hang in Python 3.8, coverage 5.0+ incompatible +cryptography >= 1.3.0, < 2.2 ; python_version < '2.7' # cryptography 2.2 drops support for python 2.6 +cryptography >= 1.3.0, < 3.4 ; python_version < '3.6' # cryptography 3.4 drops support for python 2.7 +urllib3 < 1.24 ; python_version < '2.7' # urllib3 1.24 and later require python 2.7 or later +wheel < 0.30.0 ; python_version < '2.7' # wheel 0.30.0 and later require python 2.7 or later +paramiko < 2.4.0 ; python_version < '2.7' # paramiko 2.4.0 drops support for python 2.6 +paramiko < 3.0.0 ; python_version < '3.7' # paramiko 3.0.0 forces installation of a too new cryptography +requests < 2.20.0 ; python_version < '2.7' # requests 2.20.0 drops support for python 2.6 +requests < 2.28 ; python_version < '3.7' # requests 2.28.0 drops support for python < 3.7 +virtualenv < 16.0.0 ; python_version < '2.7' # virtualenv 16.0.0 and later require python 2.7 or later +pyopenssl < 18.0.0 ; python_version < '2.7' # pyOpenSSL 18.0.0 and later require python 2.7 or later +setuptools < 45 ; python_version <= '2.7' # setuptools 45 and later require python 3.5 or later +websocket-client < 1.0.0 ; python_version <= '3.6' + +# Restrict docker versions depending on Python version +docker < 5.0.0 ; python_version <= '3.6' +docker-compose < 1.25.0 ; python_version <= '3.6' diff --git a/ansible_collections/community/docker/tests/utils/shippable/alpine.sh b/ansible_collections/community/docker/tests/utils/shippable/alpine.sh new file mode 100755 index 000000000..157dd74e1 --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/alpine.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" +pyver=default + +# check for explicit python version like 8.3@3.8 +declare -a splitversion +IFS='@' read -ra splitversion <<< "$version" + +if [ "${#splitversion[@]}" -gt 1 ]; then + version="${splitversion[0]}" + pyver="${splitversion[1]}" +fi + +if [ "${#args[@]}" -gt 2 ]; then + target="azp/${args[2]}/" +else + target="azp/" +fi + +force_python="" +if [[ "${version}" =~ -pypi-latest$ ]]; then + version="${version/-pypi-latest}" + echo 'force_docker_sdk_for_python_pypi: true' >> tests/integration/interation_config.yml +fi + +stage="${S:-prod}" +provider="${P:-default}" + +if [ "${platform}" == "rhel" ] && [[ "${version}" =~ ^8\. ]]; then + echo "pynacl >= 1.4.0, < 1.5.0; python_version == '3.6'" >> tests/utils/constraints.txt +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --python "${pyver}" --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" ${force_python} diff --git a/ansible_collections/community/docker/tests/utils/shippable/fedora.sh b/ansible_collections/community/docker/tests/utils/shippable/fedora.sh new file mode 100755 index 000000000..157dd74e1 --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/fedora.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" +pyver=default + +# check for explicit python version like 8.3@3.8 +declare -a splitversion +IFS='@' read -ra splitversion <<< "$version" + +if [ "${#splitversion[@]}" -gt 1 ]; then + version="${splitversion[0]}" + pyver="${splitversion[1]}" +fi + +if [ "${#args[@]}" -gt 2 ]; then + target="azp/${args[2]}/" +else + target="azp/" +fi + +force_python="" +if [[ "${version}" =~ -pypi-latest$ ]]; then + version="${version/-pypi-latest}" + echo 'force_docker_sdk_for_python_pypi: true' >> tests/integration/interation_config.yml +fi + +stage="${S:-prod}" +provider="${P:-default}" + +if [ "${platform}" == "rhel" ] && [[ "${version}" =~ ^8\. ]]; then + echo "pynacl >= 1.4.0, < 1.5.0; python_version == '3.6'" >> tests/utils/constraints.txt +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --python "${pyver}" --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" ${force_python} diff --git a/ansible_collections/community/docker/tests/utils/shippable/linux-community.sh b/ansible_collections/community/docker/tests/utils/shippable/linux-community.sh new file mode 100755 index 000000000..78dc10a7e --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/linux-community.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +image="${args[1]}" +python="${args[2]}" + +if [ "${#args[@]}" -gt 3 ]; then + target="azp/${args[3]}/" +else + target="azp/" +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --docker "quay.io/ansible-community/test-image:${image}" --python "${python}" diff --git a/ansible_collections/community/docker/tests/utils/shippable/linux.sh b/ansible_collections/community/docker/tests/utils/shippable/linux.sh new file mode 100755 index 000000000..9a5381f8c --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/linux.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +image="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="azp/${args[2]}/" +else + target="azp/" +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --docker "${image}" diff --git a/ansible_collections/community/docker/tests/utils/shippable/remote.sh b/ansible_collections/community/docker/tests/utils/shippable/remote.sh new file mode 100755 index 000000000..157dd74e1 --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/remote.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" +pyver=default + +# check for explicit python version like 8.3@3.8 +declare -a splitversion +IFS='@' read -ra splitversion <<< "$version" + +if [ "${#splitversion[@]}" -gt 1 ]; then + version="${splitversion[0]}" + pyver="${splitversion[1]}" +fi + +if [ "${#args[@]}" -gt 2 ]; then + target="azp/${args[2]}/" +else + target="azp/" +fi + +force_python="" +if [[ "${version}" =~ -pypi-latest$ ]]; then + version="${version/-pypi-latest}" + echo 'force_docker_sdk_for_python_pypi: true' >> tests/integration/interation_config.yml +fi + +stage="${S:-prod}" +provider="${P:-default}" + +if [ "${platform}" == "rhel" ] && [[ "${version}" =~ ^8\. ]]; then + echo "pynacl >= 1.4.0, < 1.5.0; python_version == '3.6'" >> tests/utils/constraints.txt +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --python "${pyver}" --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" ${force_python} diff --git a/ansible_collections/community/docker/tests/utils/shippable/rhel.sh b/ansible_collections/community/docker/tests/utils/shippable/rhel.sh new file mode 100755 index 000000000..157dd74e1 --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/rhel.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" +pyver=default + +# check for explicit python version like 8.3@3.8 +declare -a splitversion +IFS='@' read -ra splitversion <<< "$version" + +if [ "${#splitversion[@]}" -gt 1 ]; then + version="${splitversion[0]}" + pyver="${splitversion[1]}" +fi + +if [ "${#args[@]}" -gt 2 ]; then + target="azp/${args[2]}/" +else + target="azp/" +fi + +force_python="" +if [[ "${version}" =~ -pypi-latest$ ]]; then + version="${version/-pypi-latest}" + echo 'force_docker_sdk_for_python_pypi: true' >> tests/integration/interation_config.yml +fi + +stage="${S:-prod}" +provider="${P:-default}" + +if [ "${platform}" == "rhel" ] && [[ "${version}" =~ ^8\. ]]; then + echo "pynacl >= 1.4.0, < 1.5.0; python_version == '3.6'" >> tests/utils/constraints.txt +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --python "${pyver}" --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" ${force_python} diff --git a/ansible_collections/community/docker/tests/utils/shippable/sanity.sh b/ansible_collections/community/docker/tests/utils/shippable/sanity.sh new file mode 100755 index 000000000..04b925bbb --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/sanity.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +group="${args[1]}" + +if [ "${BASE_BRANCH:-}" ]; then + base_branch="origin/${BASE_BRANCH}" +else + base_branch="" +fi + +if [ "${group}" == "extra" ]; then + ../internal_test_tools/tools/run.py --color --bot --junit + exit +fi + +# shellcheck disable=SC2086 +ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ + --docker --base-branch "${base_branch}" \ + --allow-disabled diff --git a/ansible_collections/community/docker/tests/utils/shippable/shippable.sh b/ansible_collections/community/docker/tests/utils/shippable/shippable.sh new file mode 100755 index 000000000..2ca96b888 --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/shippable.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +ansible_version="${args[0]}" +script="${args[1]}" + +function join { + local IFS="$1"; + shift; + echo "$*"; +} + +# Ensure we can write other collections to this dir +sudo chown -R "$(whoami)" "${PWD}/../../../" + +test="$(join / "${args[@]:1}")" + +docker images ansible/ansible +docker images quay.io/ansible/* +docker ps + +for container in $(docker ps --format '{{.Image}} {{.ID}}' | grep -v -e '^drydock/' -e '^quay.io/ansible/azure-pipelines-test-container:' | sed 's/^.* //'); do + docker rm -f "${container}" || true # ignore errors +done + +docker ps + +if [ -d /home/shippable/cache/ ]; then + ls -la /home/shippable/cache/ +fi + +command -v python +python -V + +function retry +{ + # shellcheck disable=SC2034 + for repetition in 1 2 3; do + set +e + "$@" + result=$? + set -e + if [ ${result} == 0 ]; then + return ${result} + fi + echo "@* -> ${result}" + done + echo "Command '@*' failed 3 times!" + exit 255 +} + +command -v pip +pip --version +pip list --disable-pip-version-check +if [ "${ansible_version}" == "devel" ]; then + retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +else + retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check +fi + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then + export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" + SHIPPABLE_RESULT_DIR="$(pwd)/shippable" + TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/docker" + mkdir -p "${TEST_DIR}" + cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}" + cd "${TEST_DIR}" +else + export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../" +fi + +if [ "${test}" == "sanity/extra" ]; then + retry pip install junit-xml --disable-pip-version-check +fi + +# START: HACK +if [ "${test}" == "sanity/extra" ]; then + # Nothing further should be added to this list. + # This is to prevent modules or plugins in this collection having a runtime dependency on other collections. + retry git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/internal_test_tools" + # NOTE: we're installing with git to work around Galaxy being a huge PITA (https://github.com/ansible/galaxy/issues/2429) + # retry ansible-galaxy -vvv collection install community.internal_test_tools +fi + +if [ "${script}" != "sanity" ] && [ "${script}" != "units" ] && [ "${test}" != "sanity/extra" ]; then + # To prevent Python dependencies on other collections only install other collections for integration tests + retry git clone --depth=1 --single-branch https://github.com/ansible-collections/ansible.posix.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/ansible/posix" + retry git clone --depth=1 --single-branch https://github.com/ansible-collections/community.crypto.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/crypto" + retry git clone --depth=1 --single-branch https://github.com/ansible-collections/community.general.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/general" + # NOTE: we're installing with git to work around Galaxy being a huge PITA (https://github.com/ansible/galaxy/issues/2429) + # retry ansible-galaxy -vvv collection install ansible.posix + # retry ansible-galaxy -vvv collection install community.crypto + # retry ansible-galaxy -vvv collection install community.general +fi +# END: HACK + + +export PYTHONIOENCODING='utf-8' + +if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then + COVERAGE=yes + COMPLETE=yes +fi + +if [ -n "${COVERAGE:-}" ]; then + # on-demand coverage reporting triggered by setting the COVERAGE environment variable to a non-empty value + export COVERAGE="--coverage" +elif [[ "${COMMIT_MESSAGE}" =~ ci_coverage ]]; then + # on-demand coverage reporting triggered by having 'ci_coverage' in the latest commit message + export COVERAGE="--coverage" +else + # on-demand coverage reporting disabled (default behavior, always-on coverage reporting remains enabled) + export COVERAGE="--coverage-check" +fi + +if [ -n "${COMPLETE:-}" ]; then + # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value + export CHANGED="" +elif [[ "${COMMIT_MESSAGE}" =~ ci_complete ]]; then + # disable change detection triggered by having 'ci_complete' in the latest commit message + export CHANGED="" +else + # enable change detection (default behavior) + export CHANGED="--changed" +fi + +if [ "${IS_PULL_REQUEST:-}" == "true" ]; then + # run unstable tests which are targeted by focused changes on PRs + export UNSTABLE="--allow-unstable-changed" +else + # do not run unstable tests outside PRs + export UNSTABLE="" +fi + +# remove empty core/extras module directories from PRs created prior to the repo-merge +find plugins -type d -empty -print -delete + +function cleanup +{ + # for complete on-demand coverage generate a report for all files with no coverage on the "sanity/5" job so we only have one copy + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ] && [ "${test}" == "sanity/5" ]; then + stub="--stub" + # trigger coverage reporting for stubs even if no other coverage data exists + mkdir -p tests/output/coverage/ + else + stub="" + fi + + if [ -d tests/output/coverage/ ]; then + if find tests/output/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then + process_coverage='yes' # process existing coverage files + elif [ "${stub}" ]; then + process_coverage='yes' # process coverage when stubs are enabled + else + process_coverage='' + fi + + if [ "${process_coverage}" ]; then + # use python 3.7 for coverage to avoid running out of memory during coverage xml processing + # only use it for coverage to avoid the additional overhead of setting up a virtual environment for a potential no-op job + virtualenv --python /usr/bin/python3.7 ~/ansible-venv + set +ux + . ~/ansible-venv/bin/activate + set -ux + + # shellcheck disable=SC2086 + ansible-test coverage xml --color -v --requirements --group-by command --group-by version ${stub:+"$stub"} + cp -a tests/output/reports/coverage=*.xml "$SHIPPABLE_RESULT_DIR/codecoverage/" + + if [ "${ansible_version}" != "2.9" ]; then + # analyze and capture code coverage aggregated by integration test target + ansible-test coverage analyze targets generate -v "$SHIPPABLE_RESULT_DIR/testresults/coverage-analyze-targets.json" + fi + + # upload coverage report to codecov.io only when using complete on-demand coverage + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ]; then + for file in tests/output/reports/coverage=*.xml; do + flags="${file##*/coverage=}" + flags="${flags%-powershell.xml}" + flags="${flags%.xml}" + # remove numbered component from stub files when converting to tags + flags="${flags//stub-[0-9]*/stub}" + flags="${flags//=/,}" + flags="${flags//[^a-zA-Z0-9_,]/_}" + + bash <(curl -s https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh) \ + -f "${file}" \ + -F "${flags}" \ + -n "${test}" \ + -t 8450ed26-4e94-4d07-8831-d2023d6d20a3 \ + -X coveragepy \ + -X gcov \ + -X fix \ + -X search \ + -X xcode \ + || echo "Failed to upload code coverage report to codecov.io: ${file}" + done + fi + fi + fi + + if [ -d tests/output/junit/ ]; then + cp -aT tests/output/junit/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi + + if [ -d tests/output/data/ ]; then + cp -a tests/output/data/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi + + if [ -d tests/output/bot/ ]; then + cp -aT tests/output/bot/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi +} + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then trap cleanup EXIT; fi + +if [[ "${COVERAGE:-}" == "--coverage" ]]; then + timeout=60 +else + timeout=50 +fi + +ansible-test env --dump --show --timeout "${timeout}" --color -v + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then "tests/utils/shippable/check_matrix.py"; fi +"tests/utils/shippable/${script}.sh" "${test}" diff --git a/ansible_collections/community/docker/tests/utils/shippable/ubuntu.sh b/ansible_collections/community/docker/tests/utils/shippable/ubuntu.sh new file mode 100755 index 000000000..157dd74e1 --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/ubuntu.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" +pyver=default + +# check for explicit python version like 8.3@3.8 +declare -a splitversion +IFS='@' read -ra splitversion <<< "$version" + +if [ "${#splitversion[@]}" -gt 1 ]; then + version="${splitversion[0]}" + pyver="${splitversion[1]}" +fi + +if [ "${#args[@]}" -gt 2 ]; then + target="azp/${args[2]}/" +else + target="azp/" +fi + +force_python="" +if [[ "${version}" =~ -pypi-latest$ ]]; then + version="${version/-pypi-latest}" + echo 'force_docker_sdk_for_python_pypi: true' >> tests/integration/interation_config.yml +fi + +stage="${S:-prod}" +provider="${P:-default}" + +if [ "${platform}" == "rhel" ] && [[ "${version}" =~ ^8\. ]]; then + echo "pynacl >= 1.4.0, < 1.5.0; python_version == '3.6'" >> tests/utils/constraints.txt +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --python "${pyver}" --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" ${force_python} diff --git a/ansible_collections/community/docker/tests/utils/shippable/units.sh b/ansible_collections/community/docker/tests/utils/shippable/units.sh new file mode 100755 index 000000000..37685cb09 --- /dev/null +++ b/ansible_collections/community/docker/tests/utils/shippable/units.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +group="${args[1]}" + +if [[ "${COVERAGE:-}" == "--coverage" ]]; then + timeout=90 +else + timeout=30 +fi + +group1=() + +case "${group}" in + 1) options=("${group1[@]:+${group1[@]}}") ;; +esac + +ansible-test env --timeout "${timeout}" --color -v + +# shellcheck disable=SC2086 +ansible-test units --color -v --docker default ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ + "${options[@]:+${options[@]}}" \ |