Skip to content

Commit

Permalink
Merge pull request #3 from mattermost/CLD-8788
Browse files Browse the repository at this point in the history
Try to fix module failure
  • Loading branch information
stafot authored Jan 16, 2025
2 parents 593bd6d + 976094d commit 0053efb
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 83 deletions.
17 changes: 13 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
BUILD_TAG := v1.2.0
BUILD_TAG := v1.2.1
IMAGE_NAME ?= mattermost/pgbouncer-config-reload
all: build-image scan push
build-image: ## Build the docker image for mattermost-cloud
@echo Building Mattermost-cloud Docker Image
build-image: ## Build the docker image
@echo Building Docker Image
@if [ -z "$(DOCKER_USERNAME)" ] || [ -z "$(DOCKER_PASSWORD)" ]; then \
echo "DOCKER_USERNAME and/or DOCKER_PASSWORD not set. Skipping Docker login."; \
else \
echo $(DOCKER_PASSWORD) | docker login --username $(DOCKER_USERNAME) --password-stdin; \
fi
docker buildx build \
--platform linux/amd64,linux/arm64 \
. -f build/Dockerfile -t $(IMAGE_NAME):$(BUILD_TAG) \
. -f Dockerfile -t $(IMAGE_NAME):$(BUILD_TAG) \
--no-cache \
--push

build-image-locally: ## Build the docker image locally
@echo Building Docker Image Locally
docker buildx build \
--platform linux/arm64 \
. -f Dockerfile -t $(IMAGE_NAME):$(BUILD_TAG) \
--no-cache \
--load

scan:
docker scan --accept-license ${IMAGE_NAME}:${BUILD_TAG}
165 changes: 88 additions & 77 deletions pgbouncer_config_reload/cli.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#!/usr/bin/env python3
"""
Tools for monitoring changes configuration and reload pgbouncer
Tools for monitoring changes to configuration and reloading pgbouncer.
"""


import configargparse
import logging
import pyinotify
import psycopg2
import os
import sys
import signal
import time

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

__author__ = "kvelichko"
__email__ = "[email protected]"
Expand All @@ -21,114 +21,123 @@
log = logging.getLogger('configmap-reload')


class ConfigmapHandler(pyinotify.ProcessEvent):

def __init__(
self,
host,
port,
user,
password,
database='pgbouncer',
timeout=10
):
class ConfigmapHandler(FileSystemEventHandler):
"""
A watchdog event handler to reload pgbouncer when relevant files change.
"""
def __init__(self, host, port, user, password, database='pgbouncer', timeout=10):
"""
:param host - pgbouncer hostname
:param port - pgbouncer port
:param user - pgbouncer admin user
:param password - pgbouncer admin password
:param database - pgbouncer admin database
:param timeout - timeout before send reload to pgbouncer
:param host: pgbouncer hostname
:param port: pgbouncer port
:param user: pgbouncer admin user
:param password: pgbouncer admin password
:param database: pgbouncer admin database
:param timeout: wait time (seconds) before sending reload to pgbouncer
"""
super().__init__()
self.host = host
self.port = port
self.user = user
self.password = password
self.database = database
self.timeout = timeout

def pgbouncer_reload(self):
def on_created(self, event):
"""
Function for reload pgbouncer
Triggered when a file/directory is created.
If the created file's name starts with "..data",
wait and then reload pgbouncer.
"""
if not event.is_directory:
log.info(f"CREATE event: '{event.src_path}'")
if os.path.basename(event.src_path).startswith('..data'):
time.sleep(self.timeout)
self.pgbouncer_reload()

def pgbouncer_reload(self):
"""
Execute pgbouncer RELOAD.
"""
log.debug("Pgbouncer graceful reload starting...")
connection = None
log.debug("Pgbouncer gracefull reload starting...")
cursor = None
try:
connection = psycopg2.connect(
user=self.user,
password=self.password,
host=self.host,
port=self.port,
database=self.database
)
user=self.user,
password=self.password,
host=self.host,
port=self.port,
database=self.database
)
connection.set_isolation_level(0)
cursor = connection.cursor()
cursor.execute("RELOAD;")
connection.commit()
except (Exception, psycopg2.Error) as error:
log.error("Failed to RELOAD pgbouncer: %s" % (error))
log.error(f"Failed to RELOAD pgbouncer: {error}")
finally:
if (connection):
if cursor:
cursor.close()
if connection:
connection.close()
log.debug("Pgbouncer connection is closed")
log.info("Pgbouncer gracefull reloaded.")

def process_IN_CREATE(self, event):
log.info("CREATE event: '%s'" % (event.pathname))
if os.path.basename(event.pathname).startswith('..data'):
time.sleep(self.timeout)
self.pgbouncer_reload()
log.info("Pgbouncer gracefully reloaded.")


def exit_signal_handler(signum, frame):
"""
Function for logging signals and interrupt program
Handle termination signals to ensure clean shutdown.
"""
log.info("Signal '%s' received. Shutdown..."
% (signal.Signals(signum).name))
log.info(f"Signal '{signal.Signals(signum).name}' received. Shutting down...")
sys.exit()


def run(args={}):
def run(args):
"""
main function
:param args - dict of arguments
Main function that sets up the file observers and starts the watchdog loop.
"""
# watch manager
wm = pyinotify.WatchManager()
observer = Observer()

event_handler = ConfigmapHandler(
host=args.pgbouncer_host,
port=args.pgbouncer_port,
user=args.pgbouncer_user,
password=args.pgbouncer_password,
database=args.pgbouncer_database,
timeout=int(args.pgbouncer_reload_timeout)
)

for path in args.config_path.split(";"):
wm.add_watch(path, pyinotify.IN_CREATE, rec=True)

# event handler
eh = ConfigmapHandler(
args.pgbouncer_host,
args.pgbouncer_port,
args.pgbouncer_user,
args.pgbouncer_password,
args.pgbouncer_database,
int(args.pgbouncer_reload_timeout)
)
if os.path.isdir(path):
log.info(f"Watching path: {path}")
observer.schedule(event_handler, path, recursive=True)
else:
log.warning(f"Path '{path}' is not a directory or does not exist. Skipping...")

# notifier
observer.start()
log.info("Entering event loop...")
notifier = pyinotify.Notifier(wm, eh)
notifier.loop()

try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
observer.stop()
observer.join()


def main():
p = configargparse.ArgParser(
description='Tools for monitoring pgbouncer configurations'
' files and gracefull reload it.'
)
description='Tool for monitoring pgbouncer configuration files and gracefully reloading them.'
)
p.add("-v", "--verbose",
help='Verbosity (-v -vv -vvv)',
action='count',
env_var='VERBOSE',
default=0)
p.add("-c", "--config-path",
help="Semicolons separated configuration path for watching. (Ex: /etc/pgbouncer;/etc/userlist)",
help="Semicolon-separated paths to watch. (e.g. /etc/pgbouncer;/etc/userlist)",
required=True,
env_var='CONFIG_PATH')
p.add("-H", "--pgbouncer-host",
Expand All @@ -152,43 +161,45 @@ def main():
default='pgbouncer',
env_var='PGBOUNCER_DATABASE')
p.add("-t", "--pgbouncer-reload-timeout",
help="Timeout before reload configuration of pgbouncer "
"(default: 10)",
help="Timeout before reloading pgbouncer (default: 10)",
default=10,
env_var='PGBOUNCER_RELOAD_TIMEOUT')
p.add("-j", "--json-log",
action='store_true',
help="Print logs as JSON",
help="Enable JSON-formatted logs",
default=False,
env_var='LOG_JSON')

args = p.parse_args()

# Configure log
handler = logging.StreamHandler()
if args.json_log:
from pythonjsonlogger import jsonlogger
formatter = jsonlogger.JsonFormatter(
fmt="%(asctime)s %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S")
# Adjusted import for python-json-logger change
from pythonjsonlogger.json import JsonFormatter
formatter = JsonFormatter(
fmt="%(asctime)s %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
handler.setFormatter(formatter)
else:
formatter = logging.Formatter(
fmt='%(asctime)s\t%(levelname)s: %(message)s',
datefmt="%Y-%m-%d %H:%M:%S")
fmt='%(asctime)s\t%(levelname)s: %(message)s',
datefmt="%Y-%m-%d %H:%M:%S"
)
handler.setFormatter(formatter)
log.addHandler(handler)

log.addHandler(handler)
loglvl = logging.ERROR
if args.verbose > 0:
loglvl = 40 - 10*args.verbose
loglvl = max(logging.ERROR - 10 * args.verbose, logging.DEBUG)
log.setLevel(loglvl)

# Configure interrupt signal handler
# Configure interrupt signal handlers
signal.signal(signal.SIGINT, exit_signal_handler)
signal.signal(signal.SIGTERM, exit_signal_handler)

log.info("Initialization complete...")

run(args)


Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
psycopg2 >= 2.9.1
psycopg2 >= 2.9.10
python-json-logger >= 2.0.2
configargparse >= 1.5.2
pyinotify >= 0.9.6
watchdog >= 3.0.0

0 comments on commit 0053efb

Please sign in to comment.