Vendor Galaxy Roles and Collections
Some checks failed
/ Ansible Lint (push) Failing after 5m45s
/ Ansible Lint (pull_request) Failing after 4m59s

This commit is contained in:
Stefan Bethke 2026-02-06 22:07:16 +01:00
commit 2aed20393f
3553 changed files with 387444 additions and 2 deletions

View file

@ -0,0 +1,84 @@
# Copyright 2025 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 annotations
import typing as t
import pytest
from ansible_collections.community.docker.plugins.modules.docker_container_copy_into import (
parse_modern,
parse_octal_string_only,
)
@pytest.mark.parametrize(
"value, expected",
[
("0777", 0o777),
("777", 0o777),
("0o777", 0o777),
("0755", 0o755),
("755", 0o755),
("0o755", 0o755),
("0644", 0o644),
("644", 0o644),
("0o644", 0o644),
(" 0644 ", 0o644),
(" 644 ", 0o644),
(" 0o644 ", 0o644),
("-1", -1),
],
)
def test_parse_string(value: str, expected: int) -> None:
assert parse_modern(value) == expected
assert parse_octal_string_only(value) == expected
@pytest.mark.parametrize(
"value",
[
0o777,
0o755,
0o644,
12345,
123456789012345678901234567890123456789012345678901234567890,
],
)
def test_parse_int(value: int) -> None:
assert parse_modern(value) == value
with pytest.raises(TypeError, match=f"^must be an octal string, got {value}L?$"):
parse_octal_string_only(value) # type: ignore
@pytest.mark.parametrize(
"value",
[
1.0,
755.5,
[],
{},
],
)
def test_parse_bad_type(value: t.Any) -> None:
with pytest.raises(TypeError, match="^must be an octal string or an integer, got "):
parse_modern(value)
with pytest.raises(TypeError, match="^must be an octal string, got "):
parse_octal_string_only(value)
@pytest.mark.parametrize(
"value",
[
"foo",
"8",
"9",
],
)
def test_parse_bad_value(value: str) -> None:
with pytest.raises(ValueError):
parse_modern(value)
with pytest.raises(ValueError):
parse_octal_string_only(value)

View file

@ -0,0 +1,121 @@
# 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 annotations
import typing as t
import pytest
from ansible_collections.community.docker.plugins.module_utils._image_archive import (
api_image_id,
)
from ansible_collections.community.docker.plugins.modules.docker_image import (
ImageManager,
)
from ..test_support.docker_image_archive_stubbing import (
write_imitation_archive,
write_irrelevant_tar,
)
if t.TYPE_CHECKING:
from collections.abc import Callable
def assert_no_logging(msg: str) -> t.NoReturn:
raise AssertionError(f"Should not have logged anything but logged {msg}")
def capture_logging(messages: list[str]) -> Callable[[str], None]:
def capture(msg: str) -> None:
messages.append(msg)
return capture
@pytest.fixture(name="tar_file_name")
def tar_file_name_fixture(tmpdir: t.Any) -> str:
"""
Return the name of a non-existing tar file in an existing temporary directory.
"""
return tmpdir.join("foo.tar")
def test_archived_image_action_when_missing(tar_file_name: str) -> None:
fake_name = "a:latest"
fake_id = "a1"
expected = f"Archived image {fake_name} to {tar_file_name}, since none present"
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: str) -> None:
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: str) -> None:
fake_name = "c:1.2.3"
fake_id = "c3"
write_irrelevant_tar(tar_file_name)
expected = f"Archived image {fake_name} to {tar_file_name}, overwriting an unreadable archive file"
actual_log: list[str] = []
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: str) -> None:
fake_name = "d:0.0.1"
old_id = "e5"
new_id = "d4"
write_imitation_archive(tar_file_name, old_id, [fake_name])
expected = f"Archived image {fake_name} to {tar_file_name}, overwriting archive with image {old_id} named {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: str) -> None:
old_name = "hi"
new_name = "d:0.0.1"
fake_id = "d4"
write_imitation_archive(tar_file_name, fake_id, [old_name])
expected = f"Archived image {new_name} to {tar_file_name}, overwriting archive with image {fake_id} named {old_name}"
actual = ImageManager.archived_image_action(
assert_no_logging, tar_file_name, new_name, api_image_id(fake_id)
)
print(f"actual : {actual}")
print(f"expected : {expected}")
assert actual == expected

View file

@ -0,0 +1,25 @@
# Copyright 2024 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 annotations
import pytest
from ansible_collections.community.docker.plugins.modules.docker_image_build import (
_quote_csv,
)
@pytest.mark.parametrize(
"value, expected",
[
("", ""),
(" ", '" "'),
(",", '","'),
('"', '""""'),
('\rhello, "hi" !\n', '"\rhello, ""hi"" !\n"'),
],
)
def test__quote_csv(value: str, expected: str) -> None:
assert _quote_csv(value) == expected

View file

@ -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
"""Unit tests for docker_network."""
from __future__ import annotations
import typing as t
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: str, expected: t.Literal["ipv4", "ipv6"]
) -> None:
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: str) -> None:
with pytest.raises(ValueError) as e:
validate_cidr(cidr)
assert f'"{cidr}" is not a valid CIDR' == str(e.value)

View file

@ -0,0 +1,377 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/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 annotations
import typing as t
import pytest
from ansible_collections.community.docker.plugins.modules import (
docker_swarm_service,
)
APIError = pytest.importorskip("docker.errors.APIError")
def test_retry_on_out_of_sequence_error(mocker: t.Any) -> None:
run_mock = mocker.MagicMock(
side_effect=APIError(
message="",
response=None,
explanation="rpc error: code = Unknown desc = update out of sequence",
)
)
mocker.patch("time.sleep")
manager = docker_swarm_service.DockerServiceManager(client=None) # type: ignore
manager.run = run_mock # type: ignore
with pytest.raises(APIError):
manager.run_safe()
assert run_mock.call_count == 3
def test_no_retry_on_general_api_error(mocker: t.Any) -> None:
run_mock = mocker.MagicMock(
side_effect=APIError(message="", response=None, explanation="some error")
)
mocker.patch("time.sleep")
manager = docker_swarm_service.DockerServiceManager(client=None) # type: ignore
manager.run = run_mock # type: ignore
with pytest.raises(APIError):
manager.run_safe()
assert run_mock.call_count == 1
def test_get_docker_environment(mocker: t.Any) -> None:
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: [f"{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() -> None:
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() -> None:
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() -> None:
# 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"], ["sleep", "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(ValueError):
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() -> None:
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() -> None:
network_names = [
"network_1",
"network_2",
"network_3",
"network_4",
]
networks: list[str | dict[str, t.Any]] = [
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": "x"})
# test for invalid aliases type
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks(
[{"name": "test", "aliases": 1}], {"test": "x"}
)
# Test invalid aliases elements
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks(
[{"name": "test", "aliases": [1]}], {"test": "x"}
)
# Test for invalid options type
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks(
[{"name": "test", "options": 1}], {"test": "x"}
)
# Test for non existing networks
with pytest.raises(ValueError):
docker_swarm_service.get_docker_networks(
[{"name": "idontexist"}], {"test": "x"}
)
# 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"}
)