Reformat code with black and isort.

This commit is contained in:
Felix Fontein
2025-10-06 18:34:59 +02:00
parent f45232635c
commit d65d37e9e9
132 changed files with 17581 additions and 14729 deletions
@@ -7,7 +7,9 @@
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import datetime
@@ -27,14 +29,21 @@ from socketserver import ThreadingTCPServer
import pytest
import requests
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.tests.unit.plugins.module_utils._api.constants import DEFAULT_DOCKER_API_VERSION
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.tests.unit.plugins.module_utils._api.constants import (
DEFAULT_DOCKER_API_VERSION,
)
from requests.packages import urllib3
from .. import fake_api
try:
from unittest import mock
except ImportError:
@@ -44,12 +53,19 @@ except ImportError:
DEFAULT_TIMEOUT_SECONDS = constants.DEFAULT_TIMEOUT_SECONDS
def response(status_code=200, content='', headers=None, reason=None, elapsed=0,
request=None, raw=None):
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, bytes):
content = json.dumps(content).encode('ascii')
content = json.dumps(content).encode("ascii")
res._content = content
res.headers = requests.structures.CaseInsensitiveDict(headers or {})
res.reason = reason
@@ -74,7 +90,7 @@ def fake_resp(method, url, *args, **kwargs):
elif (url, method) in fake_api.fake_responses:
key = (url, method)
if not key:
raise Exception(f'{method} {url}')
raise Exception(f"{method} {url}")
status_code, content = fake_api.fake_responses[key]()
return response(status_code=status_code, content=content)
@@ -83,38 +99,38 @@ fake_request = mock.Mock(side_effect=fake_resp)
def fake_get(self, url, *args, **kwargs):
return fake_request('GET', url, *args, **kwargs)
return fake_request("GET", url, *args, **kwargs)
def fake_post(self, url, *args, **kwargs):
return fake_request('POST', url, *args, **kwargs)
return fake_request("POST", url, *args, **kwargs)
def fake_put(self, url, *args, **kwargs):
return fake_request('PUT', url, *args, **kwargs)
return fake_request("PUT", url, *args, **kwargs)
def fake_delete(self, url, *args, **kwargs):
return fake_request('DELETE', url, *args, **kwargs)
return fake_request("DELETE", url, *args, **kwargs)
def fake_read_from_socket(self, response, stream, tty=False, demux=False):
return b""
url_base = f'{fake_api.prefix}/'
url_prefix = f'{url_base}v{DEFAULT_DOCKER_API_VERSION}/'
url_base = f"{fake_api.prefix}/"
url_prefix = f"{url_base}v{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',
"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
_read_from_socket=fake_read_from_socket,
)
self.patcher.start()
self.client = APIClient(version=DEFAULT_DOCKER_API_VERSION)
@@ -123,15 +139,20 @@ class BaseAPIClientTest(unittest.TestCase):
self.client.close()
self.patcher.stop()
def base_create_payload(self, img='busybox', cmd=None):
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,
}
cmd = ["true"]
return {
"Tty": False,
"Image": img,
"Cmd": cmd,
"AttachStdin": False,
"AttachStderr": True,
"AttachStdout": True,
"StdinOnce": False,
"OpenStdin": False,
"NetworkDisabled": False,
}
class DockerApiTest(BaseAPIClientTest):
@@ -139,55 +160,48 @@ class DockerApiTest(BaseAPIClientTest):
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'
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 == f'{url_prefix}hello/somename/world'
url = self.client._url("/hello/{0}/world", "somename")
assert url == f"{url_prefix}hello/somename/world"
url = self.client._url(
'/hello/{0}/world/{1}', 'somename', 'someothername'
)
assert url == f'{url_prefix}hello/somename/world/someothername'
url = self.client._url("/hello/{0}/world/{1}", "somename", "someothername")
assert url == f"{url_prefix}hello/somename/world/someothername"
url = self.client._url('/hello/{0}/world', 'some?name')
assert url == f'{url_prefix}hello/some%3Fname/world'
url = self.client._url("/hello/{0}/world", "some?name")
assert url == f"{url_prefix}hello/some%3Fname/world"
url = self.client._url("/images/{0}/push", "localhost:5000/image")
assert url == f'{url_prefix}images/localhost:5000/image/push'
assert url == f"{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'])
self.client._url("/hello/{0}/world", ["sakuya", "izayoi"])
def test_url_no_resource(self):
url = self.client._url('/simple')
assert url == f'{url_prefix}simple'
url = self.client._url("/simple")
assert url == f"{url_prefix}simple"
def test_url_unversioned_api(self):
url = self.client._url(
'/hello/{0}/world', 'somename', versioned_api=False
)
assert url == f'{url_base}hello/somename/world'
url = self.client._url("/hello/{0}/world", "somename", versioned_api=False)
assert url == f"{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
"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
"GET", url_base + "version", timeout=DEFAULT_TIMEOUT_SECONDS
)
def test_retrieve_server_version(self):
@@ -204,95 +218,94 @@ class DockerApiTest(BaseAPIClientTest):
self.client.info()
fake_request.assert_called_with(
'GET',
url_prefix + 'info',
timeout=DEFAULT_TIMEOUT_SECONDS
"GET", url_prefix + "info", timeout=DEFAULT_TIMEOUT_SECONDS
)
def test_search(self):
self.client.get_json('/images/search', params={'term': 'busybox'})
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
"GET",
url_prefix + "images/search",
params={"term": "busybox"},
timeout=DEFAULT_TIMEOUT_SECONDS,
)
def test_login(self):
self.client.login('sakuya', 'izayoi')
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[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,
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 _socket_path_for_client_session(self, client):
socket_adapter = client.get_adapter('http+docker://')
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)
c = APIClient(base_url="unix://socket", version=DEFAULT_DOCKER_API_VERSION)
assert self._socket_path_for_client_session(c) == '/socket'
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)
c = APIClient(base_url="unix:///socket", version=DEFAULT_DOCKER_API_VERSION)
assert self._socket_path_for_client_session(c) == '/socket'
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)
base_url="http+unix:///socket", version=DEFAULT_DOCKER_API_VERSION
)
assert self._socket_path_for_client_session(c) == '/socket'
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)
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)
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})
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
"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).encode('utf-8')
status_code, content = fake_api.fake_responses[url_prefix + "events"]()
content_str = json.dumps(content).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)
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)
@@ -307,11 +320,11 @@ class DockerApiTest(BaseAPIClientTest):
assert result == content
# non-chunked response, pass `decode=False` to the helper
setattr(raw_resp._fp, 'chunked', False)
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')
assert result == content_str.decode("utf-8")
# non-chunked response, pass `decode=True` to the helper
raw_resp._fp.seek(0)
@@ -326,7 +339,7 @@ class UnixSocketStreamTest(unittest.TestCase):
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.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)
@@ -367,17 +380,17 @@ class UnixSocketStreamTest(unittest.TestCase):
self.server_socket.close()
def early_response_sending_handler(self, connection):
data = b''
data = b""
headers = None
connection.sendall(self.response)
while not headers:
data += connection.recv(2048)
parts = data.split(b'\r\n\r\n', 1)
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())
mo = re.search(r"Content-Length: ([0-9]+)", headers.decode())
assert mo
content_length = int(mo.group(1))
@@ -387,77 +400,78 @@ class UnixSocketStreamTest(unittest.TestCase):
data += connection.recv(2048)
@pytest.mark.skipif(
constants.IS_WINDOWS_PLATFORM, reason='Unix only'
)
@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 += [f'{len(line):x}'.encode(), line]
lines.append(b'0')
lines.append(b'')
lines += [f"{len(line):x}".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)
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:
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',
"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)
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)
]
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.'
"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'''
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'''
"""
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 = ThreadingTCPServer(('', 0), cls.get_handler_class())
cls.server = ThreadingTCPServer(("", 0), cls.get_handler_class())
cls.thread = threading.Thread(target=cls.server.serve_forever)
cls.thread.daemon = True
cls.thread.start()
cls.address = f'http://{socket.gethostname()}:{cls.server.server_address[1]}'
cls.address = f"http://{socket.gethostname()}:{cls.server.server_address[1]}"
@classmethod
def teardown_class(cls):
@@ -474,10 +488,9 @@ class TCPSocketStreamTest(unittest.TestCase):
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.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)
@@ -485,22 +498,22 @@ class TCPSocketStreamTest(unittest.TestCase):
self.wfile.flush()
def get_resp_data(self):
path = self.path.split('/')[-1]
if path == 'tty':
path = self.path.split("/")[-1]
if path == "tty":
return stdout_data + stderr_data
elif path == 'no-tty':
data = b''
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(f'Unknown path {path}')
raise Exception(f"Unknown path {path}")
@staticmethod
def frame_header(stream, data):
return struct.pack('>BxxxL', stream, len(data))
return struct.pack(">BxxxL", stream, len(data))
return Handler
@@ -511,12 +524,11 @@ class TCPSocketStreamTest(unittest.TestCase):
version=DEFAULT_DOCKER_API_VERSION,
) as client:
if tty:
url = client._url('/tty')
url = client._url("/tty")
else:
url = client._url('/no-tty')
url = client._url("/no-tty")
resp = client._post(url, stream=True)
return client._read_from_socket(
resp, stream=stream, tty=tty, demux=demux)
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)
@@ -565,8 +577,8 @@ class UserAgentTest(unittest.TestCase):
def setUp(self):
self.patcher = mock.patch.object(
APIClient,
'send',
return_value=fake_resp("GET", f"{fake_api.prefix}/version")
"send",
return_value=fake_resp("GET", f"{fake_api.prefix}/version"),
)
self.mock_send = self.patcher.start()
@@ -579,18 +591,16 @@ class UserAgentTest(unittest.TestCase):
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
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 = 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'
assert headers["User-Agent"] == "foo/bar"
class DisableSocketTest(unittest.TestCase):