From 48e1bdc02a7939834c4cb010196185d86dba28f7 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:36:50 +0200 Subject: [PATCH 01/27] fix: Fix some docstring --- devo/api/client.py | 1 + devo/api/scripts/client_cli.py | 2 +- devo/sender/lookup.py | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/devo/api/client.py b/devo/api/client.py index b2bff14..a4d4790 100644 --- a/devo/api/client.py +++ b/devo/api/client.py @@ -441,6 +441,7 @@ def query( :param limit: Max number of rows :param offset: start of needle for query :param comment: comment for query + :param ip_as_string: whether to recive IP types as strings :return: Result of the query (dict) or Iterator object """ dates = self._generate_dates(dates) diff --git a/devo/api/scripts/client_cli.py b/devo/api/scripts/client_cli.py index afb5ee7..6d8e5d7 100644 --- a/devo/api/scripts/client_cli.py +++ b/devo/api/scripts/client_cli.py @@ -161,7 +161,7 @@ def configure(args): """ Load CLI configuration :param args: args from files, launch vars, etc - :return: Clien t API Object and Config values in array + :return: Client API Object and Config values in array """ config = Configuration() try: diff --git a/devo/sender/lookup.py b/devo/sender/lookup.py index 0813169..3c752ed 100644 --- a/devo/sender/lookup.py +++ b/devo/sender/lookup.py @@ -392,6 +392,7 @@ def field_to_str(field, escape_quotes=False): """ Convert one value to STR, cleaning it :param field: field to clean + :param escape_quotes: whether to escape quotes in response :return: """ return ",%s" % Lookup.clean_field(field, escape_quotes) @@ -402,6 +403,7 @@ def process_fields(fields=None, key_index=None, escape_quotes=False): Method to convert list with one row/fields to STR to send :param fields: fields list :param key_index: index of key in fields + :param escape_quotes: whether to escape quotes in response :return: """ # First the key @@ -417,6 +419,7 @@ def clean_field(field=None, escape_quotes=False): """ Strip and quotechar the fields :param str field: field for clean + :param escape_quotes: whether to escape quotes in response :return str: cleaned field """ if not isinstance(field, (str, bytes)): From 2241d6cfce2c97e38749995d49addbd991c347dc Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:39:08 +0200 Subject: [PATCH 02/27] fix: auxiliary Echo server for testing improved and adapted to new async Python paradigm --- tests/integration/local_servers.py | 73 ++++++++------------ tests/integration/test_sender_cli.py | 5 +- tests/integration/test_sender_send_data.py | 32 ++++----- tests/integration/test_sender_send_lookup.py | 5 +- 4 files changed, 50 insertions(+), 65 deletions(-) diff --git a/tests/integration/local_servers.py b/tests/integration/local_servers.py index f70cf01..665beb7 100644 --- a/tests/integration/local_servers.py +++ b/tests/integration/local_servers.py @@ -32,44 +32,57 @@ def wait_for_ready_server(address, port): except socket.error: num_tries -= 1 time.sleep(1) + if num_tries == 0: + raise Exception("Connection to address %s at port %d could not be established" % (address, port)) -class SSLServer: +class EchoServer: - def __init__(self, ip="127.0.0.1", port=4488, certfile=None, keyfile=None): + def __init__(self, ip="127.0.0.1", port=4488, certfile=None, keyfile=None, ssl=True): self.ip = ip self.port = port self.cert = certfile self.key = keyfile self.shutdown = False + self.ssl = ssl self.file_path = "".join((os.path.dirname(os.path.abspath(__file__)), os.sep)) - self.server_process = multiprocessing.Process(target=self.server, name="sslserver") + self.server_process = multiprocessing.Process(target=self.server, name="sslserver" if ssl else "tcpserver") self.server_process.start() def server(self): + asyncio.run(self.run_server()) - @asyncio.coroutine - def handle_connection(reader, writer): + async def run_server(self): + + async def handle_connection(reader, writer): addr = writer.get_extra_info("peername") try: - while True: - data = yield from reader.read(500) - print("Server received {!r} from {}".format(data, addr)) + while not self.shutdown: + data = await reader.read(500) assert len(data) > 0, repr(data) + print("Server received {!r} from {}".format(data, addr)) writer.write(data) - yield from writer.drain() - except Exception: + await writer.drain() + except Exception as e: + print(f"Error: {e}") + finally: writer.close() + await writer.wait_closed() + + # Create SSL context + sc = None + if self.ssl: + sc = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + sc.load_cert_chain(self.cert, self.key) - sc = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - sc.load_cert_chain(self.cert, self.key) + # Run server + server = await asyncio.start_server(handle_connection, self.ip, self.port, ssl=sc) - loop = asyncio.get_event_loop() - coro = asyncio.start_server(handle_connection, self.ip, self.port, ssl=sc, loop=loop) - server = loop.run_until_complete(coro) + print("Serving SSL on {}".format(server.sockets[0].getsockname())) - print("Serving on {}".format(server.sockets[0].getsockname())) - loop.run_forever() + async with server: + # Server runs forever (until closed) + await server.serve_forever() def close_server(self): self.shutdown = True @@ -77,31 +90,5 @@ def close_server(self): self.server_process.join() -class TCPServer: - - def __init__(self, ip="127.0.0.1", port=4489): - self.ip = ip - self.port = port - self.shutdown = False - self.server = None - self.file_path = "".join((os.path.dirname(os.path.abspath(__file__)), os.sep)) - self.server = threading.Thread(target=self.start_server, kwargs={"ip": ip, "port": port}) - self.server.setDaemon(True) - self.server.start() - - def start_server(self): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind((self.ip, self.port)) - s.listen(5) - conn, addr = s.accept() - while not self.shutdown: - data = conn.recv(8000) - conn.send(data) - - def close_server(self): - self.shutdown = True - - if __name__ == "__main__": print("Trying to run module local_servers.py directly...") diff --git a/tests/integration/test_sender_cli.py b/tests/integration/test_sender_cli.py index 5900716..b292ce5 100644 --- a/tests/integration/test_sender_cli.py +++ b/tests/integration/test_sender_cli.py @@ -4,7 +4,7 @@ import pytest from click.testing import CliRunner -from local_servers import SSLServer, find_available_port, wait_for_ready_server +from local_servers import EchoServer, find_available_port, wait_for_ready_server from devo.common import Configuration from devo.common.generic.configuration import ConfigurationException @@ -102,11 +102,12 @@ class Fixture: setup.bad_yaml_config_path = setup.common_path + os.sep + "bad_yaml_config.yaml" setup.ssl_port = find_available_port(setup.ssl_address, setup.ssl_port) - local_ssl_server = SSLServer( + local_ssl_server = EchoServer( setup.ssl_address, setup.ssl_port, setup.local_server_cert, setup.local_server_key, + ssl=True ) wait_for_ready_server(local_ssl_server.ip, local_ssl_server.port) diff --git a/tests/integration/test_sender_send_data.py b/tests/integration/test_sender_send_data.py index dfefb43..83aa454 100644 --- a/tests/integration/test_sender_send_data.py +++ b/tests/integration/test_sender_send_data.py @@ -8,7 +8,7 @@ import pem import pytest -from local_servers import (SSLServer, TCPServer, find_available_port, +from local_servers import (EchoServer, find_available_port, wait_for_ready_server) from OpenSSL import SSL, crypto @@ -66,7 +66,7 @@ class Fixture: setup.local_server_chain = os.getenv( "DEVO_SENDER_SERVER_CHAIN", f"{setup.certs_path}/ca/ca_cert.pem" ) - setup.test_tcp = os.getenv("DEVO_TEST_TCP", False) + setup.test_tcp = (os.getenv("DEVO_TEST_TCP", False) == 'True') setup.configuration = Configuration() setup.configuration.set( "sender", @@ -101,14 +101,14 @@ class Fixture: # Run local servers # ---------------------------------------- setup.ssl_port = find_available_port(setup.ssl_address, setup.ssl_port) - local_ssl_server = SSLServer( - setup.ssl_address, setup.ssl_port, setup.local_server_cert, setup.local_server_key + local_ssl_server = EchoServer( + setup.ssl_address, setup.ssl_port, setup.local_server_cert, setup.local_server_key, ssl=True ) wait_for_ready_server(local_ssl_server.ip, local_ssl_server.port) if setup.test_tcp: setup.tcp_port = find_available_port(setup.tcp_address, setup.tcp_port) - local_tcp_server = TCPServer(setup.tcp_address, setup.tcp_port) + local_tcp_server = EchoServer(setup.tcp_address, setup.tcp_port, ssl=False) wait_for_ready_server(local_tcp_server.ip, local_tcp_server.port) yield setup @@ -262,19 +262,15 @@ def test_rt_send_no_certs(setup): """ if not setup.test_tcp: pytest.skip("Not testing TCP") - try: - engine_config = SenderConfigSSL( - address=(setup.ssl_address, setup.ssl_port), - check_hostname=False, - verify_mode=CERT_NONE, - ) - con = Sender(engine_config) - for i in range(setup.default_numbers_sendings): - con.send(tag=setup.my_app, msg=setup.test_msg) - con.close() - return True - except Exception: - return False + engine_config = SenderConfigSSL( + address=(setup.ssl_address, setup.ssl_port), + check_hostname=False, + verify_mode=CERT_NONE, + ) + con = Sender(engine_config) + for i in range(setup.default_numbers_sendings): + con.send(tag=setup.my_app, msg=setup.test_msg) + con.close() def test_sender_as_handler(setup): diff --git a/tests/integration/test_sender_send_lookup.py b/tests/integration/test_sender_send_lookup.py index 339405f..cda9f27 100644 --- a/tests/integration/test_sender_send_lookup.py +++ b/tests/integration/test_sender_send_lookup.py @@ -6,7 +6,7 @@ from unittest import mock import pytest -from local_servers import SSLServer, find_available_port, wait_for_ready_server +from local_servers import EchoServer, find_available_port, wait_for_ready_server from devo.common import Configuration from devo.common.loadenv.load_env import load_env_file @@ -97,11 +97,12 @@ class Fixture: setup.configuration.save(path=setup.config_path) setup.ssl_port = find_available_port(setup.ssl_address, setup.ssl_port) - local_ssl_server = SSLServer( + local_ssl_server = EchoServer( setup.ssl_address, setup.ssl_port, setup.local_server_cert, setup.local_server_key, + ssl=True ) wait_for_ready_server(local_ssl_server.ip, local_ssl_server.port) From e9aea9eef9f7659ec4a6afc1ff25cddac5591341 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:48:31 +0200 Subject: [PATCH 03/27] fix: lastest version of Python requires date operations to include time zones --- devo/common/dates/dateoperations.py | 9 +++++---- devo/common/dates/dateutils.py | 8 ++++++-- tests/integration/test_api_query.py | 6 +++--- tests/unit/test_common_date_parser.py | 14 ++++++++------ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/devo/common/dates/dateoperations.py b/devo/common/dates/dateoperations.py index 8e955af..3be27d9 100644 --- a/devo/common/dates/dateoperations.py +++ b/devo/common/dates/dateoperations.py @@ -2,6 +2,7 @@ """A collection of allowed operations on date parsing""" from datetime import datetime as dt +from datetime import UTC as UTC from datetime import timedelta from .dateutils import to_millis, trunc_time, trunc_time_minute @@ -60,7 +61,7 @@ def now(): Return current millis in UTC :return: Millis """ - return to_millis(dt.utcnow()) + return to_millis(dt.now(UTC)) def now_without_ms(): @@ -68,7 +69,7 @@ def now_without_ms(): Return current millis in UTC :return: Millis """ - return to_millis(trunc_time_minute(dt.utcnow())) + return to_millis(trunc_time_minute(dt.now(UTC))) def today(): @@ -76,7 +77,7 @@ def today(): Return current millis with the time truncated to 00:00:00 :return: Millis """ - return to_millis(trunc_time(dt.utcnow())) + return to_millis(trunc_time(dt.now(UTC))) def yesterday(): @@ -84,7 +85,7 @@ def yesterday(): Return millis from yesterday with time truncated to 00:00:00 :return: Millis """ - return to_millis(trunc_time(dt.utcnow()) - timedelta(days=1)) + return to_millis(trunc_time(dt.now(UTC)) - timedelta(days=1)) def parse_functions(): diff --git a/devo/common/dates/dateutils.py b/devo/common/dates/dateutils.py index bca1aa1..e57b3b0 100644 --- a/devo/common/dates/dateutils.py +++ b/devo/common/dates/dateutils.py @@ -2,6 +2,7 @@ """Utils for format and trunc dates.""" from datetime import datetime as dt +from datetime import UTC as UTC def to_millis(date): @@ -10,7 +11,10 @@ def to_millis(date): :param date: Date for parse to millis :return: Millis from the date """ - return int((date - dt.utcfromtimestamp(0)).total_seconds() * 1000) + # Verify whether param has timezone, if not set the default UTC one + if date.tzinfo is None: + date = date.replace(tzinfo=UTC) + return int((date - dt.fromtimestamp(0, UTC)).total_seconds() * 1000) def trunc_time(date): @@ -50,4 +54,4 @@ def get_timestamp(): Generate current timestamp :return: """ - return to_millis(dt.utcnow()) + return to_millis(dt.now(UTC)) diff --git a/tests/integration/test_api_query.py b/tests/integration/test_api_query.py index fd229d8..f1461ad 100644 --- a/tests/integration/test_api_query.py +++ b/tests/integration/test_api_query.py @@ -2,7 +2,7 @@ import os import tempfile import types -from datetime import datetime, timedelta +from datetime import datetime, timedelta, UTC from ssl import CERT_NONE from time import gmtime, strftime @@ -226,8 +226,8 @@ def test_query_from_fixed_dates(api_config): result = api.query( query=api_config.query, dates={ - "from": strftime("%Y-%m-%d", gmtime()), - "to": strftime("%Y-%m-%d %H:%M:%S", gmtime()), + "from": datetime.now(UTC).strftime("%Y-%m-%d"), + "to": datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"), }, ) assert result is not None diff --git a/tests/unit/test_common_date_parser.py b/tests/unit/test_common_date_parser.py index 8053036..7e71d75 100644 --- a/tests/unit/test_common_date_parser.py +++ b/tests/unit/test_common_date_parser.py @@ -1,4 +1,5 @@ from datetime import datetime as dt +from datetime import UTC as UTC import pytest @@ -7,7 +8,7 @@ @pytest.fixture(scope="module") def setup_epoch(): - epoch = dt.utcfromtimestamp(0) + epoch = dt.fromtimestamp(0, UTC) yield epoch @@ -15,13 +16,13 @@ def setup_epoch(): # -------------------------------------------------------------------------- def test_default_to(setup_epoch): ts1 = str(default_to())[:11] - ts2 = str((dt.utcnow() - setup_epoch).total_seconds() * 1000)[:11] + ts2 = str((dt.now(UTC) - setup_epoch).total_seconds() * 1000)[:11] assert ts1 == ts2 def test_default_from(setup_epoch): ts1 = str(default_from())[:11] - ts2 = str(int((dt.utcnow() - setup_epoch).total_seconds() * 1000) - 86400000)[:11] + ts2 = str(int((dt.now(UTC) - setup_epoch).total_seconds() * 1000) - 86400000)[:11] assert ts1 == ts2 @@ -54,16 +55,17 @@ def test_month(): # Tests relatives # -------------------------------------------------------------------------- def test_now(setup_epoch): - assert default_from("now()") == int((dt.utcnow() - setup_epoch).total_seconds() * 1000) + tolerance = 100 + assert abs(default_from("now()") - int((dt.now(UTC) - setup_epoch).total_seconds() * 1000)) < 100 def test_today(setup_epoch): - tmp = dt.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + tmp = dt.now(UTC).replace(hour=0, minute=0, second=0, microsecond=0) assert default_from("today()") == int((tmp - setup_epoch).total_seconds() * 1000) def test_yesterday(setup_epoch): - tmp = dt.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + tmp = dt.now(UTC).replace(hour=0, minute=0, second=0, microsecond=0) assert ( default_from("yesterday()") == int((tmp - setup_epoch).total_seconds() * 1000) - 86400000 ) From f4ea18a7dc7a793c017d55f91d9900cfe12fc214 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:49:51 +0200 Subject: [PATCH 04/27] fix: SSL wrapping for no certificates context some deprecated arguments for TLS removed non existent parameters in docstring --- devo/sender/data.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/devo/sender/data.py b/devo/sender/data.py index f72ae80..8df58b7 100644 --- a/devo/sender/data.py +++ b/devo/sender/data.py @@ -593,8 +593,6 @@ def __connect_ssl(self): context = ssl.create_default_context(cafile=self._sender_config.chain) context.options |= ssl.OP_NO_SSLv2 context.options |= ssl.OP_NO_SSLv3 - context.options |= ssl.OP_NO_TLSv1 - context.options |= ssl.OP_NO_TLSv1_1 context.minimum_version = ssl.TLSVersion.TLSv1_2 context.maximum_version = ssl.TLSVersion.TLSv1_3 @@ -614,9 +612,16 @@ def __connect_ssl(self): self.socket, server_hostname=self._sender_config.address[0] ) else: - self.socket = ssl.wrap_socket( - self.socket, ssl_version=ssl.PROTOCOL_TLS, cert_reqs=ssl.CERT_NONE - ) + context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + context.options |= ssl.OP_NO_SSLv2 + context.options |= ssl.OP_NO_SSLv3 + context.minimum_version = ssl.TLSVersion.TLSv1_2 + context.maximum_version = ssl.TLSVersion.TLSv1_3 + self.logger.warning("One or more of CA certificate, private or public certificate is not provided" + " and TLS unsecure connection is established") + self.socket = context.wrap_socket(self.socket) self.socket.connect(self._sender_config.address) self.last_message = int(time.time()) @@ -1093,7 +1098,6 @@ def for_logging(config=None, con_type=None, tag=None, level=None): :param con_type: type of connection :param tag: tag for the table :param level: level of logger - :param formatter: log formatter :return: Sender object """ con = Sender(config=config, con_type=con_type) From c38e1799741704eba5ec52ae10a60875ffbc2089 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:50:32 +0200 Subject: [PATCH 05/27] chore: Extend supported Python versions to 10, 11 and 12 some deprecated arguments for TLS removed non existent parameters in docstring --- .github/workflows/python-prereleased.yml | 2 +- .github/workflows/python-pull-request.yml | 2 +- .github/workflows/python-released.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-prereleased.yml b/.github/workflows/python-prereleased.yml index 28615bd..28b6b87 100644 --- a/.github/workflows/python-prereleased.yml +++ b/.github/workflows/python-prereleased.yml @@ -25,7 +25,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v3 with: - python-version: "3.9" + python-version: "3.12" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/python-pull-request.yml b/.github/workflows/python-pull-request.yml index 9a7000e..cd589b3 100644 --- a/.github/workflows/python-pull-request.yml +++ b/.github/workflows/python-pull-request.yml @@ -27,7 +27,7 @@ jobs: strategy: max-parallel: 1 matrix: - version: ["3.8", "3.9"] + version: ["3.8", "3.9", "3.10", "3.11", "3.12"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/python-released.yml b/.github/workflows/python-released.yml index bf37d56..f394b57 100644 --- a/.github/workflows/python-released.yml +++ b/.github/workflows/python-released.yml @@ -25,7 +25,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v3 with: - python-version: "3.9" + python-version: "3.12" - name: Install dependencies run: | python -m pip install --upgrade pip From 60c289ecb84092d9258ac08c9c896dff3cc252bb Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:57:53 +0200 Subject: [PATCH 06/27] chore: Prepare version some deprecated arguments for TLS removed non existent parameters in docstring --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- devo/__version__.py | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed38fd..5e0eeb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [5.4.1] - 2024-07-09 + +### Changed + - Extended supported Python versions to 10, 11 and 12 + - Added time zones in date operations + +### Fixed + - SSL wrapping of the TCP connection when no certificates are used + - Fix auxiliary Echo serving for unit testing in order to run with new async paradigm + - Some parameters missing or non existent in docstring + ## [5.4.0] - 2024-07-09 ### Changed diff --git a/README.md b/README.md index d5716f6..20612d6 100755 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The Devo SDK for Python requires Python 3.8+ ## Compatibility -- Tested compatibility for python 3.8 and 3.9 +- Tested compatibility for python 3.8, 3.9, 3.10, 3.11 and 3.12 ## Quick Start diff --git a/devo/__version__.py b/devo/__version__.py index a916e14..629774f 100644 --- a/devo/__version__.py +++ b/devo/__version__.py @@ -1,6 +1,6 @@ __description__ = "Devo Python Library." __url__ = "http://www.devo.com" -__version__ = "5.4.0" +__version__ = "5.4.1" __author__ = "Devo" __author_email__ = "support@devo.com" __license__ = "MIT" From 47281282dc99c70921abc641b4b12bd163f52ca0 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:04:46 +0200 Subject: [PATCH 07/27] chore: Python versions reordered to perform tests from bigger to smaller --- .github/workflows/python-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-pull-request.yml b/.github/workflows/python-pull-request.yml index cd589b3..0c6ff29 100644 --- a/.github/workflows/python-pull-request.yml +++ b/.github/workflows/python-pull-request.yml @@ -27,7 +27,7 @@ jobs: strategy: max-parallel: 1 matrix: - version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + version: [ "3.12", "3.11", "3.10", "3.9", "3.8" ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 2c7d70f189cf9636fe5fc01067c2c5bceb24d161 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Wed, 11 Sep 2024 09:54:23 +0200 Subject: [PATCH 08/27] fix: setuptools install forced in ci script --- .github/workflows/python-pull-request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-pull-request.yml b/.github/workflows/python-pull-request.yml index 0c6ff29..300d9b7 100644 --- a/.github/workflows/python-pull-request.yml +++ b/.github/workflows/python-pull-request.yml @@ -38,6 +38,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install setuptools pip install -r requirements-test.txt pip install -e . - name: Install certificates From 614eb275a377938916db40fc8cfb516374c05387 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:21:50 +0200 Subject: [PATCH 09/27] fix: Updated UTC reference in dependencies to allow support in Python <=10 --- devo/common/dates/dateoperations.py | 4 +++- devo/common/dates/dateutils.py | 3 ++- tests/integration/test_api_query.py | 6 ++++-- tests/unit/test_common_date_parser.py | 3 ++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/devo/common/dates/dateoperations.py b/devo/common/dates/dateoperations.py index 3be27d9..eb90f8e 100644 --- a/devo/common/dates/dateoperations.py +++ b/devo/common/dates/dateoperations.py @@ -2,7 +2,9 @@ """A collection of allowed operations on date parsing""" from datetime import datetime as dt -from datetime import UTC as UTC +import zoneinfo +UTC = zoneinfo.ZoneInfo("UTC") + from datetime import timedelta from .dateutils import to_millis, trunc_time, trunc_time_minute diff --git a/devo/common/dates/dateutils.py b/devo/common/dates/dateutils.py index e57b3b0..84699c9 100644 --- a/devo/common/dates/dateutils.py +++ b/devo/common/dates/dateutils.py @@ -2,7 +2,8 @@ """Utils for format and trunc dates.""" from datetime import datetime as dt -from datetime import UTC as UTC +import zoneinfo +UTC = zoneinfo.ZoneInfo("UTC") def to_millis(date): diff --git a/tests/integration/test_api_query.py b/tests/integration/test_api_query.py index f1461ad..ff0c109 100644 --- a/tests/integration/test_api_query.py +++ b/tests/integration/test_api_query.py @@ -2,9 +2,11 @@ import os import tempfile import types -from datetime import datetime, timedelta, UTC +from datetime import datetime, timedelta +import zoneinfo +UTC = zoneinfo.ZoneInfo("UTC") + from ssl import CERT_NONE -from time import gmtime, strftime import pytest import stopit diff --git a/tests/unit/test_common_date_parser.py b/tests/unit/test_common_date_parser.py index 7e71d75..92219b7 100644 --- a/tests/unit/test_common_date_parser.py +++ b/tests/unit/test_common_date_parser.py @@ -1,5 +1,6 @@ from datetime import datetime as dt -from datetime import UTC as UTC +import zoneinfo +UTC = zoneinfo.ZoneInfo("UTC") import pytest From ab1455547dfd0d625f4b4f9f69f3e01f2e94f13b Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Wed, 11 Sep 2024 14:08:08 +0200 Subject: [PATCH 10/27] fix: Removed stopit dependency in unit tests due to deprecated dependencies --- .github/workflows/python-pull-request.yml | 1 - requirements-test.txt | 4 ++-- setup.py | 3 ++- tests/integration/test_api_query.py | 24 ++++++++++++----------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/python-pull-request.yml b/.github/workflows/python-pull-request.yml index 300d9b7..0c6ff29 100644 --- a/.github/workflows/python-pull-request.yml +++ b/.github/workflows/python-pull-request.yml @@ -38,7 +38,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools pip install -r requirements-test.txt pip install -e . - name: Install certificates diff --git a/requirements-test.txt b/requirements-test.txt index 698b033..2f5f7d8 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,7 +1,7 @@ -stopit==1.1.2 msgpack~=1.0.8 responses~=0.25.3 pipdeptree~=2.23.0 pytest~=8.2.2 pytest-cov~=5.0.0 -mock==5.1.0 \ No newline at end of file +mock==5.1.0 +pebbe==5.0.7 \ No newline at end of file diff --git a/setup.py b/setup.py index c1f1669..59e7de6 100644 --- a/setup.py +++ b/setup.py @@ -36,12 +36,13 @@ ] EXTRAS_REQUIRE = { "dev": [ - "stopit==1.1.2", "msgpack~=1.0.8", "responses~=0.25.3", "pipdeptree~=2.23.0", "pytest~=8.2.2", "pytest-cov~=5.0.0", + "mock~=5.1.0", + "pebble~=5.0.7" ] } CLI = [ diff --git a/tests/integration/test_api_query.py b/tests/integration/test_api_query.py index ff0c109..3dad5fb 100644 --- a/tests/integration/test_api_query.py +++ b/tests/integration/test_api_query.py @@ -7,11 +7,10 @@ UTC = zoneinfo.ZoneInfo("UTC") from ssl import CERT_NONE - import pytest -import stopit from ip_validation import is_valid_ip - +from pebble import concurrent +from pebble import ProcessExpired from devo.api import Client, ClientConfig, DevoClientException from devo.common import Configuration from devo.common.loadenv.load_env import load_env_file @@ -272,15 +271,18 @@ def test_stream_query_no_results_unbounded_dates(api_config): result = api.query(query=api_config.query_no_results) assert isinstance(result, types.GeneratorType) + @concurrent.process(timeout=3) + def fetch_result(result): + return list(result) + + future = fetch_result(result) + try: - with stopit.ThreadingTimeout(3) as to_ctx_mgr: - result = list(result) - except DevoClientException: - # This exception is sent because - # devo.api.client.Client._make_request catches the - # stopit.TimeoutException, but the latter is not - # wrapped, so we cannot obtain it from here. - assert to_ctx_mgr.state == to_ctx_mgr.TIMED_OUT + results = future.result() + except TimeoutError as error: + assert True + except DevoClientException as error: + assert False, "DevoClientException raised: %s" % (error) def test_pragmas(api_config): From 095d457659f380d5cdbac5babbcd35c9384176aa Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Wed, 11 Sep 2024 14:09:24 +0200 Subject: [PATCH 11/27] fix: Typo in pebble dependency --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index 2f5f7d8..aabbfe1 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -4,4 +4,4 @@ pipdeptree~=2.23.0 pytest~=8.2.2 pytest-cov~=5.0.0 mock==5.1.0 -pebbe==5.0.7 \ No newline at end of file +pebble==5.0.7 \ No newline at end of file From 1dec67f1cdcc29cb0515a007337d5832de3357bd Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:01:09 +0200 Subject: [PATCH 12/27] feat: Python 3.8 support rejected --- .github/workflows/python-pull-request.yml | 2 +- README.md | 2 +- setup.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-pull-request.yml b/.github/workflows/python-pull-request.yml index 0c6ff29..a16f5e7 100644 --- a/.github/workflows/python-pull-request.yml +++ b/.github/workflows/python-pull-request.yml @@ -27,7 +27,7 @@ jobs: strategy: max-parallel: 1 matrix: - version: [ "3.12", "3.11", "3.10", "3.9", "3.8" ] + version: [ "3.12", "3.11", "3.10", "3.9" ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 20612d6..a82c47b 100755 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The Devo SDK for Python requires Python 3.8+ ## Compatibility -- Tested compatibility for python 3.8, 3.9, 3.10, 3.11 and 3.12 +- Tested compatibility for python 3.9, 3.10, 3.11 and 3.12 ## Quick Start diff --git a/setup.py b/setup.py index 59e7de6..249c62b 100644 --- a/setup.py +++ b/setup.py @@ -18,8 +18,10 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules", From 5174b86f385e69c2bed942d21e0176b2b608646d Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:01:49 +0200 Subject: [PATCH 13/27] fix: TimeoutError reference fixed --- tests/integration/test_api_query.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_api_query.py b/tests/integration/test_api_query.py index 3dad5fb..2e12907 100644 --- a/tests/integration/test_api_query.py +++ b/tests/integration/test_api_query.py @@ -10,7 +10,7 @@ import pytest from ip_validation import is_valid_ip from pebble import concurrent -from pebble import ProcessExpired +from concurrent.futures import TimeoutError from devo.api import Client, ClientConfig, DevoClientException from devo.common import Configuration from devo.common.loadenv.load_env import load_env_file @@ -279,7 +279,7 @@ def fetch_result(result): try: results = future.result() - except TimeoutError as error: + except TimeoutError: assert True except DevoClientException as error: assert False, "DevoClientException raised: %s" % (error) From 90860ce9e641c7b9483ce0be013feab6395fa735 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:41:29 +0200 Subject: [PATCH 14/27] chore: Version 6.0.0 doc amendments --- CHANGELOG.md | 6 +++++- README.md | 2 +- devo/__version__.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e0eeb9..a43735c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [5.4.1] - 2024-07-09 +## [6.0.0] - 2024-XX-XX ### Changed - Extended supported Python versions to 10, 11 and 12 @@ -14,6 +14,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - SSL wrapping of the TCP connection when no certificates are used - Fix auxiliary Echo serving for unit testing in order to run with new async paradigm - Some parameters missing or non existent in docstring + - Fix for a unit test about concurrency support of the case (from stopit to pebble) + +### Removed + - Python 3.8 support discontinued ## [5.4.0] - 2024-07-09 diff --git a/README.md b/README.md index a82c47b..ea8409b 100755 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This is the SDK to access Devo directly from Python. It can be used to: ## Requirements -The Devo SDK for Python requires Python 3.8+ +The Devo SDK for Python requires Python 3.9+ ## Compatibility diff --git a/devo/__version__.py b/devo/__version__.py index 629774f..2d099da 100644 --- a/devo/__version__.py +++ b/devo/__version__.py @@ -1,6 +1,6 @@ __description__ = "Devo Python Library." __url__ = "http://www.devo.com" -__version__ = "5.4.1" +__version__ = "6.0.0" __author__ = "Devo" __author_email__ = "support@devo.com" __license__ = "MIT" From 9ef124cc0582ea435cbf736fb9cc4ebf254cfe7d Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:17:42 +0200 Subject: [PATCH 15/27] fix: PyYaml dependency opened for bigger compatibility --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5d55fd4..9211850 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ click==8.1.7 -PyYAML==6.0.1 +PyYAML~=6.0.1 requests~=2.32 pem~=21.2.0 pyopenssl~=24.1.0 diff --git a/setup.py b/setup.py index 249c62b..bce3931 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ INSTALL_REQUIRES = [ "requests~=2.32", "click==8.1.7", - "PyYAML==6.0.1", + "PyYAML~=6.0.1", "pem~=21.2.0", "pyopenssl~=24.1.0", "pytz~=2024.1", From 64e9cf21cca5ce7ead75c1fe1a3a329794e4bd53 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:17:58 +0200 Subject: [PATCH 16/27] chore: deprecation warning added --- devo/sender/data.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/devo/sender/data.py b/devo/sender/data.py index 8df58b7..77155c6 100644 --- a/devo/sender/data.py +++ b/devo/sender/data.py @@ -15,6 +15,7 @@ from ssl import SSLWantReadError, SSLWantWriteError from threading import Thread, Lock, Event from typing import Optional, Callable +import warnings import pem from _socket import SHUT_WR @@ -648,7 +649,7 @@ def info(self, msg): """ self.send(tag=self.logging.get("tag"), msg=msg) - # TODO: Deprecated + # TODO: Deprecated, to remove in next mayor version def set_sec_level(self, sec_level=None): """ Set sec_level of SSL Context: @@ -656,9 +657,11 @@ def set_sec_level(self, sec_level=None): :param sec_level: sec_level value :return """ + warnings.warn("This function is deprecated and it will be removed in future versions", + DeprecationWarning, stacklevel=2) self._sender_config.sec_level = sec_level - # TODO: Deprecated + # TODO: Deprecated, to remove in next mayor version def set_verify_mode(self, verify_mode=None): """ Set verify_mode of SSL Context: @@ -670,9 +673,11 @@ def set_verify_mode(self, verify_mode=None): :param verify_mode: verify mode value :return """ + warnings.warn("This function is deprecated and it will be removed in future versions", + DeprecationWarning, stacklevel=2) self._sender_config.verify_mode = verify_mode - # TODO: Deprecated + # TODO: Deprecated, to remove in next mayor version def set_check_hostname(self, check_hostname=True): """ Set check_hostname of SSL Context: @@ -680,6 +685,8 @@ def set_check_hostname(self, check_hostname=True): :param check_hostname: check_hostname value. Default True :return """ + warnings.warn("This function is deprecated and it will be removed in future versions", + DeprecationWarning, stacklevel=2) self._sender_config.check_hostname = check_hostname def buffer_size(self, size=19500): From 28c2579747e958a335f0c0fdfc1cbe8a55a8b373 Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Wed, 2 Oct 2024 18:00:37 +0200 Subject: [PATCH 17/27] test: improve pytest verbosity --- .github/workflows/python-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-pull-request.yml b/.github/workflows/python-pull-request.yml index a16f5e7..25c1d89 100644 --- a/.github/workflows/python-pull-request.yml +++ b/.github/workflows/python-pull-request.yml @@ -61,4 +61,4 @@ jobs: export DEVO_SENDER_CHAIN=$(realpath certs/us/ca.crt) export TMPDIR=${PWD} cd tests - python -m pytest + python -m pytest -vvv From 8aecca0ebbf294301ff770a54727685dbc5bb3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ram=C3=B3n=20Afonso?= Date: Wed, 2 Oct 2024 13:23:29 +0200 Subject: [PATCH 18/27] Fix[AWS Jobs]: Fixed destination jobs in AWS/Serrea --- CHANGELOG.md | 1 + devo/api/client.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a43735c..6635baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Added time zones in date operations ### Fixed + - `destination` parameter its not working, forcing NO_KEEPALIVE_TOKEN to avoid problems in client jobs creation - SSL wrapping of the TCP connection when no certificates are used - Fix auxiliary Echo serving for unit testing in order to run with new async paradigm - Some parameters missing or non existent in docstring diff --git a/devo/api/client.py b/devo/api/client.py index a4d4790..ce01faf 100644 --- a/devo/api/client.py +++ b/devo/api/client.py @@ -173,6 +173,7 @@ def __init__( self.processor = None self.set_processor(processor) self.keepAliveToken = None + self.set_keepalive_token(keepAliveToken) if pragmas: @@ -235,7 +236,9 @@ def set_keepalive_token(self, keepAliveToken=DEFAULT_KEEPALIVE_TOKEN): # keepalive (cannot be modified), but implementation uses # NO_KEEP_ALIVE value as it does not change the query msgpack and # xls does not support keepalive - if self.response in [ + if self.destination is not None: + self.keepAliveToken = NO_KEEPALIVE_TOKEN + elif self.response in [ "json", "json/compact", "json/simple", @@ -254,7 +257,6 @@ def set_keepalive_token(self, keepAliveToken=DEFAULT_KEEPALIVE_TOKEN): self.keepAliveToken = NO_KEEPALIVE_TOKEN return True - class Client: """ The Devo search rest api main class From 24b30ac21edc47fb49f48ce504fe0684a450fadc Mon Sep 17 00:00:00 2001 From: JuanFran Adame <9873247+franjuan@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:50:18 +0200 Subject: [PATCH 19/27] fix: Added error message missing ERROR_AFTER_TIMEOUT --- devo/sender/data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/devo/sender/data.py b/devo/sender/data.py index 77155c6..b831430 100644 --- a/devo/sender/data.py +++ b/devo/sender/data.py @@ -75,6 +75,7 @@ def __str__(self): RAW_SENDING_ERROR = ("Error sending raw event: %s",) CLOSING_ERROR = "Error closing connection" FLUSHING_BUFFER_ERROR = "Error flushing buffer" + ERROR_AFTER_TIMEOUT = "Timeout reached" class DevoSenderException(Exception): From 24d0be7d0027db474a792f643886b8e41c29eedf Mon Sep 17 00:00:00 2001 From: JuanFran Adame <9873247+franjuan@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:44:26 +0200 Subject: [PATCH 20/27] fix: Jobs API review and fix --- CHANGELOG.md | 1 + devo/api/client.py | 12 ++----- tests/integration/test_api_tasks.py | 53 ++++++++++++++++------------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6635baa..542219e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Changed - Extended supported Python versions to 10, 11 and 12 - Added time zones in date operations + - Jobs API reviewed and fixed ### Fixed - `destination` parameter its not working, forcing NO_KEEPALIVE_TOKEN to avoid problems in client jobs creation diff --git a/devo/api/client.py b/devo/api/client.py index ce01faf..85777ab 100644 --- a/devo/api/client.py +++ b/devo/api/client.py @@ -758,18 +758,10 @@ def _generate_pragmas(self, comment=None): return str_pragmas - def get_jobs(self, job_type=None, name=None): + def get_jobs(self): """Get list of jobs by type and name, default All - :param job_type: category of jobs - :param name: name of jobs :return: json""" - plus = ( - "" - if not job_type - else "/{}".format(job_type if not name else "{}/{}".format(job_type, name)) - ) - - return self._call_jobs("{}{}{}".format(self.address[0], "/search/jobs", plus)) + return self._call_jobs("{}{}".format(self.address[0], "/search/jobs")) def get_job(self, job_id): """Get all info of job diff --git a/tests/integration/test_api_tasks.py b/tests/integration/test_api_tasks.py index 3159938..957df6f 100644 --- a/tests/integration/test_api_tasks.py +++ b/tests/integration/test_api_tasks.py @@ -1,5 +1,6 @@ +import json import os - +import uuid import pytest from devo.api import Client @@ -10,7 +11,7 @@ @pytest.fixture(scope="module") -def setup_client(): +def setup_client(job_name): yield Client( config={ "key": os.getenv("DEVO_API_KEY", None), @@ -19,7 +20,7 @@ def setup_client(): "stream": False, "destination": { "type": "donothing", - "params": {"friendlyName": "devo-sdk-api-test"}, + "params": {"friendlyName": job_name}, }, } ) @@ -29,39 +30,43 @@ def setup_client(): def setup_query(): yield "from siem.logtrust.web.connection select action" +@pytest.fixture(scope="module") +def job_name(): + return "devo-sdk-api-test" + str(uuid.uuid1().int) + -@pytest.mark.skip("temporarily disabled due to Query API bug") -def test_jobs_cycle(setup_client, setup_query): - setup_client.query(query=setup_query, dates={"from": "1d"}) +#@pytest.mark.skip("temporarily disabled due to Query API bug") +def test_jobs_cycle(setup_client, setup_query, job_name): + result = setup_client.query(query=setup_query, dates={"from": "1d", "to": "endday"}) + result = json.loads(result) + assert result["status"]==0 + assert "object" in result + assert "id" in result["object"] + job_id=result["object"]["id"] # Get all jobs result = setup_client.get_jobs() - assert result["object"] is True + assert len(result["object"])>0 - # Get job by name - result = setup_client.get_jobs(name="devo-sdk-api-test") - assert result["object"] is True - - # Get job by type - result = setup_client.get_jobs(job_type="donothing") - assert result["object"] is True - - # Get job by name and type - result = setup_client.get_jobs(name="devo-sdk-api-test", job_type="donothing") - assert result["object"] is True - job_id = result["object"][0]["id"] + # Get job by job id + result = setup_client.get_job(job_id=job_id) + assert result["object"]["friendlyName"] == job_name + assert result["object"]["id"] == job_id # Stop job by id result = setup_client.stop_job(job_id) - assert result["object"]["status"] == "STOPPED" + assert result["object"]["status"] in ["STOPPED","COMPLETED"] # Start job by id result = setup_client.start_job(job_id) - assert result["object"]["status"] == "RUNNING" + assert result["object"]["status"] in ["RUNNING","COMPLETED"] - # Delete job by id - result = setup_client.remove_job(job_id) - assert result["object"]["status"] == "REMOVED" + # Delete all jobs created by this and past test execution (cleaning purposes) + result = setup_client.get_jobs() + jobs_ids = [job["id"] for job in result["object"] if job["friendlyName"].startswith("devo-sdk-api-test")] + for job_id in jobs_ids: + result = setup_client.remove_job(job_id) + assert result["object"]["status"] == "REMOVED" if __name__ == "__main__": From 3aed0e8046cf549cca16d6b301c507bf531d55cb Mon Sep 17 00:00:00 2001 From: JuanFran <9873247+franjuan@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:04:03 +0200 Subject: [PATCH 21/27] test: Added timeout to unit test of queries to API --- CHANGELOG.md | 1 + requirements-test.txt | 3 ++- tests/integration/test_api_cli.py | 4 ++++ tests/integration/test_api_query.py | 13 +++++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 542219e..d338345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Extended supported Python versions to 10, 11 and 12 - Added time zones in date operations - Jobs API reviewed and fixed + - Added timeout to unit test of queries to API ### Fixed - `destination` parameter its not working, forcing NO_KEEPALIVE_TOKEN to avoid problems in client jobs creation diff --git a/requirements-test.txt b/requirements-test.txt index aabbfe1..0e50fe7 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -4,4 +4,5 @@ pipdeptree~=2.23.0 pytest~=8.2.2 pytest-cov~=5.0.0 mock==5.1.0 -pebble==5.0.7 \ No newline at end of file +pebble==5.0.7 +pytest-timeout~=2.3.1 \ No newline at end of file diff --git a/tests/integration/test_api_cli.py b/tests/integration/test_api_cli.py index 1d06227..b2ac2b5 100644 --- a/tests/integration/test_api_cli.py +++ b/tests/integration/test_api_cli.py @@ -204,6 +204,7 @@ def test_bad_credentials(api_config): assert result.exception.code == 12 +@pytest.mark.timeout(180) def test_normal_query(api_config): runner = CliRunner() result = runner.invoke( @@ -228,6 +229,7 @@ def test_normal_query(api_config): assert '{"m":{"eventdate":{"type":"timestamp","index":0' in result.output +@pytest.mark.timeout(180) def test_with_config_file(api_config): if api_config.config_path: runner = CliRunner() @@ -249,6 +251,7 @@ def test_with_config_file(api_config): assert '{"m":{"eventdate":{"type":"timestamp","index":0' in result.output +@pytest.mark.timeout(180) def test_query_with_ip_as_int(api_config): runner = CliRunner() result = runner.invoke( @@ -278,6 +281,7 @@ def test_query_with_ip_as_int(api_config): assert isinstance(resp_data["d"][0], int) +@pytest.mark.timeout(180) def test_query_with_ip_as_str(api_config): runner = CliRunner() result = runner.invoke( diff --git a/tests/integration/test_api_query.py b/tests/integration/test_api_query.py index 2e12907..fcce23c 100644 --- a/tests/integration/test_api_query.py +++ b/tests/integration/test_api_query.py @@ -151,6 +151,7 @@ def test_from_dict(api_config): assert isinstance(api, Client) +@pytest.mark.timeout(180) def test_simple_query(api_config): config = ClientConfig(stream=False, response="json") @@ -166,6 +167,7 @@ def test_simple_query(api_config): assert len(json.loads(result)["object"]) > 0 +@pytest.mark.timeout(180) def test_token(api_config): api = Client( auth={"token": api_config.api_token}, @@ -178,6 +180,7 @@ def test_token(api_config): assert len(json.loads(result)["object"]) > 0 +@pytest.mark.timeout(180) def test_query_id(api_config): api = Client( auth={"key": api_config.api_key, "secret": api_config.api_secret}, @@ -191,6 +194,7 @@ def test_query_id(api_config): assert isinstance(len(json.loads(result)["object"]), int) +@pytest.mark.timeout(180) def test_query_yesterday_to_today(api_config): api = Client( auth={"key": api_config.api_key, "secret": api_config.api_secret}, @@ -205,6 +209,7 @@ def test_query_yesterday_to_today(api_config): assert len(json.loads(result)["object"]) == 1 +@pytest.mark.timeout(180) def test_query_from_seven_days(api_config): api = Client( auth={"key": api_config.api_key, "secret": api_config.api_secret}, @@ -217,6 +222,7 @@ def test_query_from_seven_days(api_config): assert len(json.loads(result)["object"]) == 1 +@pytest.mark.timeout(180) def test_query_from_fixed_dates(api_config): api = Client( auth={"key": api_config.api_key, "secret": api_config.api_secret}, @@ -235,6 +241,7 @@ def test_query_from_fixed_dates(api_config): assert len(json.loads(result)["object"]) == 1 +@pytest.mark.timeout(180) def test_stream_query(api_config): api = Client( auth={"key": api_config.api_key, "secret": api_config.api_secret}, @@ -248,6 +255,7 @@ def test_stream_query(api_config): assert len(result) == 1 +@pytest.mark.timeout(180) def test_stream_query_no_results_bounded_dates(api_config): api = Client( auth={"key": api_config.api_key, "secret": api_config.api_secret}, @@ -285,6 +293,7 @@ def fetch_result(result): assert False, "DevoClientException raised: %s" % (error) +@pytest.mark.timeout(180) def test_pragmas(api_config): """Test the api when the pragma comment.free is used""" api = Client( @@ -300,6 +309,7 @@ def test_pragmas(api_config): assert len(json.loads(result)["object"]) == 1 +@pytest.mark.timeout(180) def test_pragmas_not_comment_free(api_config): """Test the api when the pragma comment.free is not used""" api = Client( @@ -316,6 +326,7 @@ def test_pragmas_not_comment_free(api_config): @pytest.mark.skip(reason="This is an internal functionality, not intended for external use") +@pytest.mark.timeout(180) def test_unsecure_http_query(api_config): """ This test is intended for checking unsecure HTTP requests. Devo will @@ -436,6 +447,7 @@ def test_msgpack_future_queries(api_config): ) +@pytest.mark.timeout(180) def test_query_with_ip_as_int(api_config): config = ClientConfig(stream=False, response="json") @@ -454,6 +466,7 @@ def test_query_with_ip_as_int(api_config): assert isinstance(resp_data[api_config.field_with_ip], int) +@pytest.mark.timeout(180) def test_query_with_ip_as_string(api_config): config = ClientConfig(stream=False, response="json") From bee754f42eb1dfa846772aa6893b687e862cba76 Mon Sep 17 00:00:00 2001 From: JuanFran Adame <9873247+franjuan@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:02:56 +0200 Subject: [PATCH 22/27] chore: CHANGELOG.md review --- CHANGELOG.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd9321..8105d38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,19 +9,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Changed - Extended supported Python versions to 10, 11 and 12 - Added time zones in date operations - - Jobs API reviewed and fixed - - Added timeout to unit test of queries to API + - Jobs API reviewed and fixed. Jobs searching by type and friendlyName discontinued as it is not supported by API. + Jobs API unit test checked and enabled + - Added timeout to unit tests of API queries. They may run forever when faulty ### Fixed - - `destination` parameter its not working, forcing NO_KEEPALIVE_TOKEN to avoid problems in client jobs creation - - SSL wrapping of the TCP connection when no certificates are used + - Keep-alive mechanism not working for queries with `destination`. Forcing NO_KEEPALIVE_TOKEN in queries with + `destination`. + - SSL wrapping of the TCP connection when no certificates are used improved - Fix auxiliary Echo serving for unit testing in order to run with new async paradigm - - Some parameters missing or non existent in docstring - - Fix for a unit test about concurrency support of the case (from stopit to pebble) + - Documentation fixes. Some parameters missing or non-existent in docstring + - Fix for a unit test when using concurrency (from library `stopit` to `pebble`) ### Removed - Python 3.8 support discontinued +### Incompatibilities with 5.x.x that caused mayor version bump + - Python 3.8 not supported anymore + - Jobs searching by type and friendlyName discontinued in Jobs API. Only search by id filtering is supported. + - Date requires time zone + - Query with `destination` are forced to NO_KEEPALIVE_TOKEN mode for Keep-alive mechanism (instead of + DEFAULT_KEEPALIVE_TOKEN) + ## [5.4.1] - 2024-09-13 ### Security From a75cfc1f3a628c3f3cbe4f5ea2f0ae8c5ba551fc Mon Sep 17 00:00:00 2001 From: JuanFran Adame <9873247+franjuan@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:09:02 +0200 Subject: [PATCH 23/27] fix: PEP8 formatting --- tests/integration/test_api_query.py | 4 ++-- tests/integration/test_api_tasks.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration/test_api_query.py b/tests/integration/test_api_query.py index fcce23c..5aa86c4 100644 --- a/tests/integration/test_api_query.py +++ b/tests/integration/test_api_query.py @@ -4,8 +4,6 @@ import types from datetime import datetime, timedelta import zoneinfo -UTC = zoneinfo.ZoneInfo("UTC") - from ssl import CERT_NONE import pytest from ip_validation import is_valid_ip @@ -16,6 +14,8 @@ from devo.common.loadenv.load_env import load_env_file from devo.sender.data import Sender, SenderConfigSSL +UTC = zoneinfo.ZoneInfo("UTC") + # Load environment variables form test directory load_env_file(os.path.abspath(os.getcwd()) + os.sep + "environment.env") diff --git a/tests/integration/test_api_tasks.py b/tests/integration/test_api_tasks.py index 957df6f..b70dab5 100644 --- a/tests/integration/test_api_tasks.py +++ b/tests/integration/test_api_tasks.py @@ -30,23 +30,23 @@ def setup_client(job_name): def setup_query(): yield "from siem.logtrust.web.connection select action" + @pytest.fixture(scope="module") def job_name(): return "devo-sdk-api-test" + str(uuid.uuid1().int) -#@pytest.mark.skip("temporarily disabled due to Query API bug") def test_jobs_cycle(setup_client, setup_query, job_name): result = setup_client.query(query=setup_query, dates={"from": "1d", "to": "endday"}) result = json.loads(result) - assert result["status"]==0 + assert result["status"] == 0 assert "object" in result assert "id" in result["object"] - job_id=result["object"]["id"] + job_id = result["object"]["id"] # Get all jobs result = setup_client.get_jobs() - assert len(result["object"])>0 + assert len(result["object"]) > 0 # Get job by job id result = setup_client.get_job(job_id=job_id) @@ -55,11 +55,11 @@ def test_jobs_cycle(setup_client, setup_query, job_name): # Stop job by id result = setup_client.stop_job(job_id) - assert result["object"]["status"] in ["STOPPED","COMPLETED"] + assert result["object"]["status"] in ["STOPPED", "COMPLETED"] # Start job by id result = setup_client.start_job(job_id) - assert result["object"]["status"] in ["RUNNING","COMPLETED"] + assert result["object"]["status"] in ["RUNNING", "COMPLETED"] # Delete all jobs created by this and past test execution (cleaning purposes) result = setup_client.get_jobs() From ab39f0ad48a1d4afcdfdc578f3d9d14580ab5596 Mon Sep 17 00:00:00 2001 From: JuanFran Adame <9873247+franjuan@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:09:46 +0200 Subject: [PATCH 24/27] fix: PEP8 formatting --- tests/integration/test_api_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_api_tasks.py b/tests/integration/test_api_tasks.py index b70dab5..3406fce 100644 --- a/tests/integration/test_api_tasks.py +++ b/tests/integration/test_api_tasks.py @@ -46,7 +46,7 @@ def test_jobs_cycle(setup_client, setup_query, job_name): # Get all jobs result = setup_client.get_jobs() - assert len(result["object"]) > 0 + assert len(result["object"]) > 0 # Get job by job id result = setup_client.get_job(job_id=job_id) From 84777b4c869b48478582e449d168f7b4cb96de9b Mon Sep 17 00:00:00 2001 From: JuanFran Adame <9873247+franjuan@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:24:42 +0200 Subject: [PATCH 25/27] fix: `destination` no keep alive --- devo/api/client.py | 5 +++++ docs/api/api.md | 1 + 2 files changed, 6 insertions(+) diff --git a/devo/api/client.py b/devo/api/client.py index 85777ab..3645c18 100644 --- a/devo/api/client.py +++ b/devo/api/client.py @@ -55,6 +55,8 @@ "connection_error": "Failed to establish a new connection", "other_errors": "Error while invoking query", "error_no_detail": "Error code %d while invoking query", + "no_keepalive_for_destination": "Queries with destination functionality only support No Keepalive mode. Forced to" + " NO_KEEP_ALIVE" } DEFAULT_KEEPALIVE_TOKEN = "\n" @@ -236,8 +238,11 @@ def set_keepalive_token(self, keepAliveToken=DEFAULT_KEEPALIVE_TOKEN): # keepalive (cannot be modified), but implementation uses # NO_KEEP_ALIVE value as it does not change the query msgpack and # xls does not support keepalive + # Queries with destination only supports NO_KEEP_ALIVE if self.destination is not None: self.keepAliveToken = NO_KEEPALIVE_TOKEN + if keepAliveToken not in [NO_KEEPALIVE_TOKEN, DEFAULT_KEEPALIVE_TOKEN]: + logging.warning(ERROR_MSGS["no_keepalive_for_destination"]) elif self.response in [ "json", "json/compact", diff --git a/docs/api/api.md b/docs/api/api.md index eda9942..0239db8 100644 --- a/docs/api/api.md +++ b/docs/api/api.md @@ -496,6 +496,7 @@ Client support several modes for supporting this mechanism. The mode is set up i * `json`, `json/compact`, `json/simple` and `json/simple/compact` token is always `b' '` (four utf-8 spaces chars) * For `csv` and `tsv` token is the custom `str` set as parameter * `msgpack` and `xls` do not support this mode +* Queries using `destination` functionality does not support keep alive. NO_KEEP_ALIVE is forced | Response mode | default mode | `NO_KEEPALIVE_TOKEN` | `DEFAULT_KEEPALIVE_TOKEN` | `EMPTY_EVENT_KEEPALIVE_TOKEN` | Custom keep alive token | |---------------------|---------------------------|----------------------|---------------------------|-------------------------------|------------------------------| From 6adac2d527e99fafc20a9dfccbe2894d6fdf2f19 Mon Sep 17 00:00:00 2001 From: JuanFran Adame <9873247+franjuan@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:28:40 +0200 Subject: [PATCH 26/27] fix: status when task is removed --- tests/integration/test_api_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_api_tasks.py b/tests/integration/test_api_tasks.py index 3406fce..5dfbe7f 100644 --- a/tests/integration/test_api_tasks.py +++ b/tests/integration/test_api_tasks.py @@ -66,7 +66,7 @@ def test_jobs_cycle(setup_client, setup_query, job_name): jobs_ids = [job["id"] for job in result["object"] if job["friendlyName"].startswith("devo-sdk-api-test")] for job_id in jobs_ids: result = setup_client.remove_job(job_id) - assert result["object"]["status"] == "REMOVED" + assert result["object"]["status"] in ["REMOVED", "COMPLETED"] if __name__ == "__main__": From 00cadec152b7867184ca93431771c2c0b7e437d2 Mon Sep 17 00:00:00 2001 From: JuanFran Adame <9873247+franjuan@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:05:57 +0200 Subject: [PATCH 27/27] chore: CHANGELOG.md review --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8105d38..da1e9fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [6.0.0] - 2024-XX-XX +## [6.0.0] - 2024-10-07 ### Changed - - Extended supported Python versions to 10, 11 and 12 + - Supported Python versions extended to 10, 11 and 12 - Added time zones in date operations - Jobs API reviewed and fixed. Jobs searching by type and friendlyName discontinued as it is not supported by API. Jobs API unit test checked and enabled - Added timeout to unit tests of API queries. They may run forever when faulty ### Fixed - - Keep-alive mechanism not working for queries with `destination`. Forcing NO_KEEPALIVE_TOKEN in queries with + - Keep-alive mechanism not working for queries with `destination`. Forcing NO_KEEP_ALIVE in queries with `destination`. - SSL wrapping of the TCP connection when no certificates are used improved - Fix auxiliary Echo serving for unit testing in order to run with new async paradigm @@ -26,9 +26,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Incompatibilities with 5.x.x that caused mayor version bump - Python 3.8 not supported anymore - - Jobs searching by type and friendlyName discontinued in Jobs API. Only search by id filtering is supported. + - Jobs searching by type and friendlyName discontinued in Jobs API. Only search by job id is supported. - Date requires time zone - - Query with `destination` are forced to NO_KEEPALIVE_TOKEN mode for Keep-alive mechanism (instead of + - Query with `destination` are forced to NO_KEEP_ALIVE mode for Keep-alive mechanism (instead of DEFAULT_KEEPALIVE_TOKEN) ## [5.4.1] - 2024-09-13