1
0
Fork 0

Some fixes and documentation

- Introduced ACME HTTP-01 challenge documentation.
- Added Docker Compose example for HTTP-01 setup.
- Updated `get_haproxy_command` logic with improved PID validation using `psutil`.
- Enhanced logging with a new `SingleLineNonEmptyFilter`.
- Expanded tests for daemonize functionalities.
- Updated dependencies to include `psutil`.
This commit is contained in:
Joao Gilberto Magalhaes 2025-08-24 16:46:30 -04:00
parent b4b02916af
commit ea9e4ee7c8
8 changed files with 125 additions and 24 deletions

View file

@ -2,12 +2,12 @@ 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 requests
from OpenSSL import crypto
@ -120,6 +120,7 @@ class Functions:
log_source_handler = logging.StreamHandler(sys.stdout)
log_source_formatter = logging.Formatter('%(name)s [%(asctime)s] %(levelname)s - %(message)s')
log_source_handler.setFormatter(log_source_formatter)
log_source_handler.addFilter(SingleLineNonEmptyFilter())
source.setLevel(selected_level)
source.addHandler(log_source_handler)
return selected_level
@ -202,18 +203,26 @@ class DaemonizeHAProxy:
if len(list(self.get_custom_config_files().keys())) != 0:
custom_config_files = "-f %s" % self.custom_config_folder
if action == DaemonizeHAProxy.HAPROXY_START:
if action == DaemonizeHAProxy.HAPROXY_START or not os.path.exists(pid_file):
return "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg %s -p %s -S /var/run/haproxy.sock" % (custom_config_files, pid_file)
else:
return_code, output = Functions().run_bash(loggerHaproxy, "cat %s" % pid_file, log_output=False)
pid = "".join(output)
return "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg %s -p %s -x /var/run/haproxy.sock -sf %s" % (custom_config_files, pid_file, pid)
pid = "".join(output).rstrip()
if psutil.pid_exists(int(pid)):
return "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg %s -p %s -x /var/run/haproxy.sock -sf %s" % (custom_config_files, pid_file, pid)
else:
os.unlink(pid_file)
loggerHaproxy.warning(
"PID file %s does not exist. Restarting haproxy instead of reload." % pid_file
)
return self.get_haproxy_command(DaemonizeHAProxy.HAPROXY_START, pid_file)
def __prepare(self, command):
if not isinstance(command, (list, tuple)):
command = shlex.split(command)
try:
loggerHaproxy.debug("HAPROXY command: %s" % command)
self.process = subprocess.Popen(command,
shell=False,
stdout=subprocess.PIPE,
@ -228,7 +237,7 @@ class DaemonizeHAProxy:
try:
with self.process.stdout:
for line in iter(self.process.stdout.readline, b''):
loggerHaproxy.info(line)
loggerHaproxy.info(line.rstrip())
return_code = self.process.wait()
loggerHaproxy.debug("Return code %s" % return_code)
@ -354,6 +363,11 @@ class Certbot:
if self.certbot_manual_auth_hook:
certbot_certonly += ' --manual --manual-auth-hook \'{hook}\''.format(hook=self.certbot_manual_auth_hook)
if loggerCertbot.level == logging.DEBUG:
certbot_certonly += ' -v'
loggerCertbot.debug("certbot_certonly: %s" % certbot_certonly)
ret_reload = False
return_code_issue = 0
return_code_renew = 0
@ -424,6 +438,38 @@ class Certbot:
self.freeze_issue[host] = self.retry_count
loggerCertbot.debug("Freeze issuing ssl for %s due failure. The certificate is %s" % (host, cert_status))
class SingleLineNonEmptyFilter(logging.Filter):
"""
Logging filter that ensures messages are single-line and non-empty.
- Collapses newlines into spaces and strips surrounding whitespace.
- Drops the record if the resulting message is empty.
"""
def filter(self, record: logging.LogRecord) -> int:
try:
msg = record.getMessage()
except Exception:
# If formatting fails, drop the record
return 0
# Convert any non-string to string representation
if not isinstance(msg, str):
msg = str(msg)
# Collapse multi-line to single line and trim
sanitized = " ".join(msg.splitlines()).strip()
if sanitized == "":
return 0
# If we changed the message, update the record and clear args
if sanitized != record.getMessage():
record.msg = sanitized
record.args = ()
return 1
# ####################################################################################################################
# Setup Global Log
loggerInit = logging.getLogger(Functions.INIT_LOG)

View file

@ -5,4 +5,5 @@ pytest
docker
kubernetes
deepdiff
pyopenssl
pyopenssl
psutil

View file

@ -11,11 +11,14 @@
acl is_rule_{{ host }}_2 hdr(host) -i {{ k }}:{{ o["port"] }}
{% if certbot %}
acl is_certbot_{{ host }} path_beg /.well-known/acme-challenge/
use_backend certbot_backend if is_certbot_{{ host }} is_rule_{{ host }}_1 OR is_certbot_{{ host }} is_rule_{{ host }}_2
{% endif %}
{% if o["hosts"][k]["redirect_ssl"] %}
http-request redirect scheme https code 301 if {% if certbot %}!is_certbot_{{ host }} {% endif %}is_rule_{{ host }}_1 OR {% if certbot %}!is_certbot_{{ host }} {% endif %}is_rule_{{ host }}_2
{% else %}
{% endif %}
{% if certbot %}
use_backend certbot_backend if is_certbot_{{ host }} is_rule_{{ host }}_1 OR is_certbot_{{ host }} is_rule_{{ host }}_2
{% endif %}
{% if not o["hosts"][k]["redirect_ssl"] %}
use_backend srv_{{ host }} if is_rule_{{ host }}_1 OR is_rule_{{ host }}_2
{% endif %}
{% endfor %}

View file

@ -51,8 +51,8 @@ frontend http_in_80
acl is_rule_test_example_org_80_1 hdr(host) -i test.example.org
acl is_rule_test_example_org_80_2 hdr(host) -i test.example.org:80
acl is_certbot_test_example_org_80 path_beg /.well-known/acme-challenge/
use_backend certbot_backend if is_certbot_test_example_org_80 is_rule_test_example_org_80_1 OR is_certbot_test_example_org_80 is_rule_test_example_org_80_2
http-request redirect scheme https code 301 if !is_certbot_test_example_org_80 is_rule_test_example_org_80_1 OR !is_certbot_test_example_org_80 is_rule_test_example_org_80_2
use_backend certbot_backend if is_certbot_test_example_org_80 is_rule_test_example_org_80_1 OR is_certbot_test_example_org_80 is_rule_test_example_org_80_2
acl is_rule_test2_example_org_80_1 hdr(host) -i test2.example.org
acl is_rule_test2_example_org_80_2 hdr(host) -i test2.example.org:80

View file

@ -1,5 +1,7 @@
import os
import psutil
from functions import DaemonizeHAProxy, Functions
@ -17,10 +19,31 @@ def test_daemonize_haproxy_get_haproxy_command_start():
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_START)
assert command == "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock"
def test_daemonize_haproxy_get_haproxy_command_reload():
def test_daemonize_haproxy_get_haproxy_command_reload_nopid():
daemon = DaemonizeHAProxy()
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_RELOAD)
assert command == "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -x /var/run/haproxy.sock -sf "
assert command == "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock"
def test_daemonize_haproxy_get_haproxy_command_reload_pidinvalid():
daemon = DaemonizeHAProxy()
try:
with open("/tmp/temp.pid", 'w') as file:
file.write("-1001")
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_RELOAD, "/tmp/temp.pid")
assert command == "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /tmp/temp.pid -S /var/run/haproxy.sock"
finally:
assert not os.path.exists("/tmp/temp.pid")
def test_daemonize_haproxy_get_haproxy_command_reload_existing_pin():
daemon = DaemonizeHAProxy()
try:
with open("/tmp/temp.pid", 'w') as file:
file.write("1")
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_RELOAD, "/tmp/temp.pid")
assert command == "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /tmp/temp.pid -x /var/run/haproxy.sock -sf 1"
finally:
assert os.path.exists("/tmp/temp.pid")
os.unlink("/tmp/temp.pid")
def test_daemonize_haproxy2_check_config():
daemon = DaemonizeHAProxy(os.path.abspath(os.path.dirname(__file__)) + '/fixtures')
@ -34,15 +57,3 @@ def test_daemonize_haproxy2_get_haproxy_command_start():
daemon = DaemonizeHAProxy(os.path.abspath(os.path.dirname(__file__)) + '/fixtures')
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_START)
assert command == "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -f %s -p /run/haproxy.pid -S /var/run/haproxy.sock" % (os.path.dirname(__file__) + "/fixtures")
def test_daemonize_haproxy2_get_haproxy_command_reload():
tmp_pid_file = "/tmp/tmp_pid.txt"
Functions.save(tmp_pid_file, "10")
try:
daemon = DaemonizeHAProxy(os.path.abspath(os.path.dirname(__file__)) + '/fixtures')
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_RELOAD, tmp_pid_file)
assert command == "/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -f %s -p %s -x /var/run/haproxy.sock -sf %s" % (os.path.dirname(__file__) + "/fixtures", tmp_pid_file, 10)
finally:
os.remove(tmp_pid_file)