33import argparse
44import fnmatch
55import logging
6- import logging .handlers
76import os
7+ import re
88import shutil
99import signal
1010import subprocess
3232 MAIN_CONFIG_DIR ,
3333 NGINX ,
3434 NGINX_PID_FILE ,
35+ SYNC_IGNORE_FILES ,
3536 UNPRIVILEGED_GID ,
3637 UNPRIVILEGED_UID ,
3738 WATCH_IGNORE_FILES ,
@@ -46,11 +47,12 @@ class NginxConfigReloader(FileSystemEventHandler):
4647 def __init__ (
4748 self ,
4849 logger = None ,
49- no_magento_config = False ,
50- no_custom_config = False ,
51- dir_to_watch = DIR_TO_WATCH ,
52- magento2_flag = None ,
53- use_systemd = False ,
50+ no_magento_config : bool = False ,
51+ no_custom_config : bool = False ,
52+ dir_to_watch : str = DIR_TO_WATCH ,
53+ magento2_flag : str | None = None ,
54+ use_systemd : bool = False ,
55+ error_file : str = ERROR_FILE ,
5456 ):
5557 """Constructor called by ProcessEvent
5658
@@ -59,6 +61,7 @@ def __init__(
5961 :param bool no_custom_config: True if we should not copy custom configuration
6062 :param str dir_to_watch: The directory to watch
6163 :param str magento2_flag: Magento 2 flag location
64+ :param str error_file: File name for error output file
6265 """
6366 if not logger :
6467 self .logger = logging
@@ -76,6 +79,7 @@ def __init__(
7679 self .dirty = False
7780 self .applying = False
7881 self ._on_config_reload = Signal ()
82+ self .error_file = error_file
7983
8084 def on_deleted (self , event ):
8185 """Triggered by inotify on removal of file or removal of dir
@@ -113,7 +117,8 @@ def handle_event(self, event):
113117 return
114118
115119 basename = os .path .basename (event .src_path )
116- if not any (fnmatch .fnmatch (basename , pat ) for pat in WATCH_IGNORE_FILES ):
120+ ignore_files = list (WATCH_IGNORE_FILES ) + [self .error_file ]
121+ if not any (fnmatch .fnmatch (basename , pat ) for pat in ignore_files ):
117122 self .logger .debug (
118123 f"{ event .event_type .upper ()} detected on { event .src_path } "
119124 )
@@ -153,37 +158,38 @@ def check_no_forbidden_config_directives_are_present(self):
153158 True if forbidden config directives are present
154159 False if check couldn't find any forbidden config flags
155160 """
156- if os .path .isdir (self .dir_to_watch ):
157- for rules in FORBIDDEN_CONFIG_REGEX :
158- try :
159- # error file may contain messages that match a forbidden config pattern
160- # then validation could fail while the actual config is correct.
161- # we'll exclude the error file from searching for patterns,
162- # NOTE: exclusion of error_file requires to ensure the
163- # file is removed before moving it to nginx conf dir
164- # @TODO: use Python to search for forbidden configs instead
165- # of spawning external procs. Will have better testing
166- # and even may consume less system resources
167- check_external_resources = (
168- "[ $(grep -r --exclude={} -P '{}' '{}' | wc -l) -lt 1 ]" .format (
169- ERROR_FILE , rules [0 ], self .dir_to_watch
170- )
171- )
172- subprocess .check_output (check_external_resources , shell = True )
173- except subprocess .CalledProcessError :
174- error = f"Unable to load config: { rules [1 ]} "
175- self .logger .error (error )
176- self .write_error_file (error )
177- return True
161+ if not os .path .isdir (self .dir_to_watch ):
178162 return False
179163
164+ for pattern , message in FORBIDDEN_CONFIG_REGEX :
165+ try :
166+ # error file may contain messages that match a forbidden config pattern
167+ # then validation could fail while the actual config is correct.
168+ # we'll exclude the error file from searching for patterns,
169+ # NOTE: exclusion of error_file requires to ensure the
170+ # file is removed before moving it to nginx conf dir
171+ # @TODO: use Python to search for forbidden configs instead
172+ # of spawning external procs. Will have better testing
173+ # and even may consume less system resources
174+ check_external_resources = "[ $(grep -r --exclude={} --exclude={} -P '{}' '{}' | wc -l) -lt 1 ]" .format (
175+ ERROR_FILE , self .error_file , pattern , self .dir_to_watch
176+ )
177+ subprocess .check_output (check_external_resources , shell = True )
178+ except subprocess .CalledProcessError :
179+ error = f"Unable to load config: { message } "
180+ self .logger .error (error )
181+ self .write_error_file (error )
182+ return True
183+
184+ return False
185+
180186 def remove_error_file (self ):
181187 """Try removing the error file. Return True on success or False on errors
182188 :rtype: bool
183189 """
184190 removed = False
185191 try :
186- os .unlink (os .path .join (self .dir_to_watch , ERROR_FILE ))
192+ os .unlink (os .path .join (self .dir_to_watch , self . error_file ))
187193 removed = True
188194 except OSError :
189195 pass
@@ -282,7 +288,8 @@ def install_new_custom_config_dir(self):
282288 if os .path .exists (CUSTOM_CONFIG_DIR ):
283289 shutil .move (CUSTOM_CONFIG_DIR , BACKUP_CONFIG_DIR )
284290 os .mkdir (CUSTOM_CONFIG_DIR )
285- safe_copy_files (self .dir_to_watch , CUSTOM_CONFIG_DIR )
291+ ignore_files = list (SYNC_IGNORE_FILES ) + [self .error_file ]
292+ safe_copy_files (self .dir_to_watch , CUSTOM_CONFIG_DIR , ignore_files )
286293
287294 def restore_old_custom_config_dir (self ):
288295 shutil .rmtree (CUSTOM_CONFIG_DIR )
@@ -308,7 +315,7 @@ def get_nginx_pid(self):
308315 return None
309316
310317 def write_error_file (self , error ):
311- with open (os .path .join (self .dir_to_watch , ERROR_FILE ), "w" ) as f :
318+ with open (os .path .join (self .dir_to_watch , self . error_file ), "w" ) as f :
312319 f .write (error )
313320
314321 @property
@@ -366,6 +373,7 @@ def wait_loop(
366373 recursive_watch = False ,
367374 use_systemd = False ,
368375 no_dbus = False ,
376+ error_file : str = ERROR_FILE ,
369377):
370378 """Main event loop
371379
@@ -382,6 +390,7 @@ def wait_loop(
382390 :param bool recursive_watch: True if we should watch the dir recursively
383391 :param use_systemd: True if we should reload nginx using systemd instead of process signal
384392 :param bool no_dbus: True if we should not use DBus
393+ :param str error_file: Error file to write error output to
385394 :return None:
386395 """
387396 dir_to_watch = os .path .abspath (dir_to_watch )
@@ -392,6 +401,7 @@ def wait_loop(
392401 no_custom_config = no_custom_config ,
393402 dir_to_watch = dir_to_watch ,
394403 use_systemd = use_systemd ,
404+ error_file = error_file ,
395405 )
396406
397407 if not no_dbus :
@@ -474,6 +484,11 @@ def parse_nginx_config_reloader_arguments():
474484 help = "Disable DBus interface" ,
475485 default = False ,
476486 )
487+ parser .add_argument (
488+ "--error-file" ,
489+ help = "File name for error output" ,
490+ default = ERROR_FILE ,
491+ )
477492 return parser .parse_args ()
478493
479494
@@ -491,6 +506,11 @@ def main():
491506 args = parse_nginx_config_reloader_arguments ()
492507 log = get_logger ()
493508
509+ error_file_pattern = re .compile (r"[a-zA-Z0-9_\.]*[a-zA-Z0-9_]+" )
510+ if not error_file_pattern .fullmatch (args .error_file ):
511+ log .error (f"Invalid error file name provided: { args .error_file } " )
512+ return 1
513+
494514 if args .monitor :
495515 # Track changed files in the nginx config dir and reload on change
496516 wait_loop (
@@ -501,6 +521,7 @@ def main():
501521 recursive_watch = args .recursivewatch ,
502522 use_systemd = args .use_systemd ,
503523 no_dbus = args .no_dbus ,
524+ error_file = args .error_file ,
504525 )
505526 # should never return
506527 return 1
@@ -512,6 +533,7 @@ def main():
512533 no_custom_config = args .nocustomconfig ,
513534 dir_to_watch = args .watchdir ,
514535 use_systemd = args .use_systemd ,
536+ error_file = args .error_file ,
515537 ).apply_new_config ()
516538 return 0
517539
0 commit comments