diff --git a/Makefile b/Makefile index f8ee00a..4d38e9e 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,10 @@ sync: lint: uv run ruff check src/ tests/ +.PHONY: fix +fix: + uv run ruff check --fix src/ tests/ + .PHONY: format format: uv run ruff format src/ tests/ diff --git a/src/easymapping/__init__.py b/src/easymapping/__init__.py index 77930a4..b80c2a1 100644 --- a/src/easymapping/__init__.py +++ b/src/easymapping/__init__.py @@ -4,6 +4,7 @@ import os import re from jinja2 import Environment, FileSystemLoader + from functions import loggerEasyHaproxy @@ -17,7 +18,7 @@ class DockerLabelHandler: def create(self, key): if isinstance(key, str): - return "{}.{}".format(self.__label_base, key) + return f"{self.__label_base}.{key}" return "{}.{}".format(self.__label_base, ".".join(key)) @@ -140,7 +141,7 @@ class HaproxyConfigGenerator: self.label.set_data(d) - # Parse each definition found. + # Parse each definition found. for definition in sorted(definitions.keys()): mode = self.label.get( self.label.create([definition, "mode"]), @@ -209,7 +210,7 @@ class HaproxyConfigGenerator: if socket_path: server_address = socket_path else: - server_address = "{}:{}".format(container, ct_port) + server_address = f"{container}:{ct_port}" easymapping[port]["hosts"][hostname]["containers"] += [server_address] easymapping[port]["hosts"][hostname]["certbot"] = certbot @@ -306,7 +307,7 @@ class HaproxyConfigGenerator: # handle SSL ssl_label = self.label.create([definition, "sslcert"]) if self.label.has_label(ssl_label): - filename = "{}.pem".format(d[host_label]) + filename = f"{d[host_label]}.pem" easymapping[port]["ssl"] = True if not clone_to_ssl else False self.certs[filename] = base64.b64decode(d[ssl_label]).decode('ascii') diff --git a/src/functions/__init__.py b/src/functions/__init__.py index 51f22d1..412a753 100644 --- a/src/functions/__init__.py +++ b/src/functions/__init__.py @@ -1,16 +1,18 @@ +import logging import os import shlex import subprocess import sys -import psutil import time -import logging from datetime import datetime from multiprocessing import Process from typing import Final + +import psutil import requests from OpenSSL import crypto + class ContainerEnv: @staticmethod def read(): @@ -150,7 +152,7 @@ class Functions: @staticmethod def load(filename): - with open(filename, 'r') as content_file: + with open(filename) as content_file: return content_file.read() @staticmethod @@ -384,7 +386,7 @@ class Certbot: ) if self.certbot_manual_auth_hook: - certbot_certonly += ' --manual --manual-auth-hook \'{hook}\''.format(hook=self.certbot_manual_auth_hook) + certbot_certonly += f' --manual --manual-auth-hook \'{self.certbot_manual_auth_hook}\'' if loggerCertbot.level == logging.DEBUG: certbot_certonly += ' -v' diff --git a/src/main.py b/src/main.py index 7af2bbf..5e80fda 100644 --- a/src/main.py +++ b/src/main.py @@ -2,8 +2,14 @@ import os from deepdiff import DeepDiff -from functions import Functions, DaemonizeHAProxy, Certbot, Consts, loggerInit, loggerEasyHaproxy, loggerHaproxy, \ - loggerCertbot +from functions import ( + Certbot, + Consts, + DaemonizeHAProxy, + Functions, + loggerEasyHaproxy, + loggerInit, +) from processor import ProcessorInterface @@ -69,7 +75,7 @@ def main(): loggerInit.debug('Environment:') for name, value in os.environ.items(): if "HAPROXY" in name: - loggerInit.debug("- {0}: {1}".format(name, value)) + loggerInit.debug(f"- {name}: {value}") start() diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py index e822d92..de06fba 100644 --- a/src/plugins/__init__.py +++ b/src/plugins/__init__.py @@ -1,10 +1,11 @@ -import os import importlib.util +import os import sys from abc import ABC, abstractmethod from dataclasses import dataclass, field from enum import Enum -from typing import Optional, Dict, Any, List +from typing import Any, Dict, List, Optional + from functions import loggerEasyHaproxy @@ -20,17 +21,17 @@ class PluginContext: parsed_object: dict # {IP: labels} from discovery easymapping: list # Current HAProxy mapping structure container_env: dict # Environment configuration - domain: Optional[str] = None # Domain name (for DOMAIN plugins) - port: Optional[str] = None # Port (for DOMAIN plugins) - host_config: Optional[dict] = None # Domain-specific config + domain: str | None = None # Domain name (for DOMAIN plugins) + port: str | None = None # Port (for DOMAIN plugins) + host_config: dict | None = None # Domain-specific config @dataclass class PluginResult: """Plugin execution result""" haproxy_config: str = "" # HAProxy config snippet to inject - modified_easymapping: Optional[list] = None # Modified easymapping structure - metadata: Dict[str, Any] = field(default_factory=dict) # Plugin metadata for logging + modified_easymapping: list | None = None # Modified easymapping structure + metadata: dict[str, Any] = field(default_factory=dict) # Plugin metadata for logging class PluginInterface(ABC): @@ -85,9 +86,9 @@ class PluginManager: """ self.plugins_dir = plugins_dir self.abort_on_error = abort_on_error - self.plugins: Dict[str, PluginInterface] = {} - self.global_plugins: List[PluginInterface] = [] - self.domain_plugins: List[PluginInterface] = [] + self.plugins: dict[str, PluginInterface] = {} + self.global_plugins: list[PluginInterface] = [] + self.domain_plugins: list[PluginInterface] = [] self.logger = loggerEasyHaproxy def load_plugins(self) -> None: @@ -174,7 +175,7 @@ class PluginManager: except Exception as e: self._handle_error(f"Failed to configure plugin '{plugin_name}': {str(e)}") - def execute_global_plugins(self, context: PluginContext, enabled_list: Optional[List[str]] = None) -> List[PluginResult]: + def execute_global_plugins(self, context: PluginContext, enabled_list: list[str] | None = None) -> list[PluginResult]: """ Execute all global plugins @@ -205,7 +206,7 @@ class PluginManager: return results - def execute_domain_plugins(self, context: PluginContext, enabled_list: Optional[List[str]] = None) -> List[PluginResult]: + def execute_domain_plugins(self, context: PluginContext, enabled_list: list[str] | None = None) -> list[PluginResult]: """ Execute all domain plugins for a specific domain diff --git a/src/plugins/builtin/cleanup.py b/src/plugins/builtin/cleanup.py index 45ef13d..e0a7c23 100644 --- a/src/plugins/builtin/cleanup.py +++ b/src/plugins/builtin/cleanup.py @@ -21,16 +21,16 @@ Example Environment Variable: EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME=600 """ +import glob import os import sys -import glob import time # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from plugins import PluginInterface, PluginType, PluginContext, PluginResult from functions import loggerEasyHaproxy +from plugins import PluginContext, PluginInterface, PluginResult, PluginType class CleanupPlugin(PluginInterface): diff --git a/src/plugins/builtin/cloudflare.py b/src/plugins/builtin/cloudflare.py index f8cb0be..490c208 100644 --- a/src/plugins/builtin/cloudflare.py +++ b/src/plugins/builtin/cloudflare.py @@ -33,8 +33,8 @@ import sys # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from plugins import PluginInterface, PluginType, PluginContext, PluginResult from functions import loggerEasyHaproxy +from plugins import PluginContext, PluginInterface, PluginResult, PluginType class CloudflarePlugin(PluginInterface): diff --git a/src/plugins/builtin/deny_pages.py b/src/plugins/builtin/deny_pages.py index 5335751..321255b 100644 --- a/src/plugins/builtin/deny_pages.py +++ b/src/plugins/builtin/deny_pages.py @@ -32,7 +32,7 @@ import sys # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from plugins import PluginInterface, PluginType, PluginContext, PluginResult +from plugins import PluginContext, PluginInterface, PluginResult, PluginType class DenyPagesPlugin(PluginInterface): diff --git a/src/plugins/builtin/fastcgi.py b/src/plugins/builtin/fastcgi.py index c50206f..0a047b7 100644 --- a/src/plugins/builtin/fastcgi.py +++ b/src/plugins/builtin/fastcgi.py @@ -42,8 +42,7 @@ import sys # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from plugins import PluginInterface, PluginType, PluginContext, PluginResult -from functions import loggerEasyHaproxy +from plugins import PluginContext, PluginInterface, PluginResult, PluginType class FastcgiPlugin(PluginInterface): @@ -124,7 +123,7 @@ class FastcgiPlugin(PluginInterface): # PATH_INFO support if self.path_info: - fcgi_app_lines.append(f" path-info ^(/.+\\.php)(/.*)?$") + fcgi_app_lines.append(" path-info ^(/.+\\.php)(/.*)?$") # Set SCRIPT_FILENAME if customized if self.script_filename and self.script_filename != "%[path]": diff --git a/src/plugins/builtin/ip_whitelist.py b/src/plugins/builtin/ip_whitelist.py index b54265c..6265afc 100644 --- a/src/plugins/builtin/ip_whitelist.py +++ b/src/plugins/builtin/ip_whitelist.py @@ -33,7 +33,7 @@ import sys # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from plugins import PluginInterface, PluginType, PluginContext, PluginResult +from plugins import PluginContext, PluginInterface, PluginResult, PluginType class IpWhitelistPlugin(PluginInterface): diff --git a/src/plugins/builtin/jwt_validator.py b/src/plugins/builtin/jwt_validator.py index 5a7b4af..b8fb5d0 100644 --- a/src/plugins/builtin/jwt_validator.py +++ b/src/plugins/builtin/jwt_validator.py @@ -74,8 +74,8 @@ import sys # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from plugins import PluginInterface, PluginType, PluginContext, PluginResult from functions import loggerEasyHaproxy +from plugins import PluginContext, PluginInterface, PluginResult, PluginType class JwtValidatorPlugin(PluginInterface): diff --git a/src/processor/__init__.py b/src/processor/__init__.py index 3e363a3..3bad432 100644 --- a/src/processor/__init__.py +++ b/src/processor/__init__.py @@ -8,8 +8,7 @@ from kubernetes import client, config from kubernetes.client.rest import ApiException from easymapping import HaproxyConfigGenerator -from functions import Functions, Consts, ContainerEnv -from functions import loggerEasyHaproxy +from functions import Consts, ContainerEnv, Functions, loggerEasyHaproxy class ProcessorInterface: @@ -87,7 +86,7 @@ class ProcessorInterface: def save_certs(self, path): for cert in self.get_certs(): - Functions.save("{0}/{1}".format(path, cert), self.get_certs(cert)) + Functions.save(f"{path}/{cert}", self.get_certs(cert)) class Static(ProcessorInterface): @@ -502,7 +501,7 @@ class Kubernetes(ProcessorInterface): if tls.secret_name not in self.cert_cache or self.cert_cache[tls.secret_name] != secret.data: self.cert_cache[tls.secret_name] = secret.data Functions.save( - "{0}/{1}.pem".format(Consts.certs_haproxy, tls.secret_name), + f"{Consts.certs_haproxy}/{tls.secret_name}.pem", base64.b64decode(secret.data["tls.crt"]).decode('ascii') + "\n" + base64.b64decode( secret.data["tls.key"]).decode('ascii') ) diff --git a/tests/test_containerenv.py b/tests/test_containerenv.py index 8fecdd3..71cea2e 100644 --- a/tests/test_containerenv.py +++ b/tests/test_containerenv.py @@ -1,6 +1,6 @@ import os -from functions import Functions, ContainerEnv +from functions import ContainerEnv, Functions def test_container_env_empty(): diff --git a/tests/test_daemonize.py b/tests/test_daemonize.py index 8f5bf1d..cf589ef 100644 --- a/tests/test_daemonize.py +++ b/tests/test_daemonize.py @@ -1,8 +1,6 @@ import os -import psutil - -from functions import DaemonizeHAProxy, Functions +from functions import DaemonizeHAProxy def test_daemonize_haproxy(): diff --git a/tests/test_functions.py b/tests/test_functions.py index 1189d20..129777f 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -1,14 +1,11 @@ import logging import os import random -import re import string -from logging import Logger - -from functions import Functions, loggerEasyHaproxy, loggerCertbot, loggerHaproxy - from io import StringIO +from functions import Functions, loggerCertbot, loggerEasyHaproxy, loggerHaproxy + log_stream = StringIO() # Create StringIO object log_handler = logging.StreamHandler(log_stream) log_formatter = logging.Formatter('%(levelname)s - %(message)s') diff --git a/tests/test_labels.py b/tests/test_labels.py index 14ce279..8b64429 100644 --- a/tests/test_labels.py +++ b/tests/test_labels.py @@ -22,7 +22,7 @@ def test_label_data(): def test_label_complex_key(): label = DockerLabelHandler("till") - + data = dict() data["till.definitions"] = "h2" data["till.host.h2"] = "fqdn.example.org" diff --git a/tests/test_parser.py b/tests/test_parser.py index 519ffa0..f418ea8 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -12,7 +12,7 @@ CERTBOT_EMAIL = "some@email.com" def load_fixture(file): path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/fixtures/" + file, 'r') as content_file: + with open(path + "/fixtures/" + file) as content_file: line_list = json.loads("".join(content_file.readlines())) return line_list @@ -33,7 +33,7 @@ def test_parser_doesnt_crash(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/no-services.txt", 'r') as expected_file: + with open(path + "/expected/no-services.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts @@ -56,7 +56,7 @@ def test_parser_finds_services(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/services.txt", 'r') as expected_file: + with open(path + "/expected/services.txt") as expected_file: assert expected_file.read() == haproxy_config assert {"www.somehost.com.br.pem": "Some PEM Certificate"} == cfg.certs @@ -86,7 +86,7 @@ def test_parser_finds_services_changed_label(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/services.txt", 'r') as expected_file: + with open(path + "/expected/services.txt") as expected_file: assert expected_file.read() == haproxy_config assert {"www.somehost.com.br.pem": "Some PEM Certificate"} == cfg.certs @@ -232,21 +232,21 @@ def test_parser_finds_services_raw(): def test_parser_static(): path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/fixtures/static.yml", 'r') as content_file: + with open(path + "/fixtures/static.yml") as content_file: parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader) cfg = easymapping.HaproxyConfigGenerator(parsed) haproxy_config = cfg.generate() assert len(haproxy_config) > 0 - with open(path + "/expected/static.txt", 'r') as expected_file: + with open(path + "/expected/static.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts def test_parser_static_raw(): path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/fixtures/static.yml", 'r') as content_file: + with open(path + "/fixtures/static.yml") as content_file: parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader) expected = { @@ -319,7 +319,7 @@ def test_parser_tcp(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/services-tcp.txt", 'r') as expected_file: + with open(path + "/expected/services-tcp.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts @@ -339,7 +339,7 @@ def test_parser_multi_containers(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/services-multi-containers.txt", 'r') as expected_file: + with open(path + "/expected/services-multi-containers.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts @@ -361,7 +361,7 @@ def test_parser_multiple_hosts(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/services-multiple-hosts.txt", 'r') as expected_file: + with open(path + "/expected/services-multiple-hosts.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts @@ -382,7 +382,7 @@ def test_parser_redirect_ssl(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/services-redirect-ssl.txt", 'r') as expected_file: + with open(path + "/expected/services-redirect-ssl.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts @@ -403,7 +403,7 @@ def test_parser_ssl_strict(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/ssl-strict.txt", 'r') as expected_file: + with open(path + "/expected/ssl-strict.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts @@ -421,7 +421,7 @@ def test_parser_ssl_loose(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/ssl-loose.txt", 'r') as expected_file: + with open(path + "/expected/ssl-loose.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts @@ -444,7 +444,7 @@ def test_parser_ssl_letsencrypt(): assert len(haproxy_config) > 0 path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/services-letsencrypt.txt", 'r') as expected_file: + with open(path + "/expected/services-letsencrypt.txt") as expected_file: assert expected_file.read() == haproxy_config assert ["test.example.org"] == cfg.certbot_hosts @@ -561,7 +561,7 @@ def test_parser_fcgi(): assert "172.17.0.3:9000" in haproxy_config path = os.path.dirname(os.path.realpath(__file__)) - with open(path + "/expected/services-fcgi.txt", 'r') as expected_file: + with open(path + "/expected/services-fcgi.txt") as expected_file: assert expected_file.read() == haproxy_config assert [] == cfg.certbot_hosts diff --git a/tests/test_plugins.py b/tests/test_plugins.py index ae53f4c..f5775da 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -7,29 +7,29 @@ Tests all builtin plugins: - DenyPagesPlugin (domain) """ +import json import os import sys -import json import tempfile import time # Add src to path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from plugins import PluginManager, PluginContext -from plugins.builtin.cloudflare import CloudflarePlugin +import easymapping +from plugins import PluginContext, PluginManager from plugins.builtin.cleanup import CleanupPlugin +from plugins.builtin.cloudflare import CloudflarePlugin from plugins.builtin.deny_pages import DenyPagesPlugin +from plugins.builtin.fastcgi import FastcgiPlugin from plugins.builtin.ip_whitelist import IpWhitelistPlugin from plugins.builtin.jwt_validator import JwtValidatorPlugin -from plugins.builtin.fastcgi import FastcgiPlugin -import easymapping def load_fixture(file): """Load a test fixture""" fixture_path = os.path.join(os.path.dirname(__file__), "fixtures", file) - with open(fixture_path, 'r') as content_file: + with open(fixture_path) as content_file: line_list = json.loads("".join(content_file.readlines())) return line_list @@ -160,7 +160,7 @@ class TestCloudflarePlugin: assert os.path.exists(ip_list_path) # Verify file contains correct number of IPs - with open(ip_list_path, 'r') as f: + with open(ip_list_path) as f: lines = [line.strip() for line in f if line.strip()] assert len(lines) == 22 # Verify some known Cloudflare IPs are in the file