Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd56f8ca63 | ||
|
|
77fabf28f6 | ||
|
|
0038663bc6 | ||
|
|
6489e4452e | ||
|
|
bfd1efa9f9 | ||
|
|
293d7972f1 | ||
|
|
d30226d55b | ||
|
|
9e454a83d0 | ||
|
|
34cadcf9b1 | ||
|
|
a9d6794c4d | ||
|
|
1fb13fefff |
@@ -2,4 +2,4 @@
|
||||
|
||||
ignored-modules = zmq
|
||||
max-line-length = 150
|
||||
disable = missing-docstring, too-few-public-methods
|
||||
disable = missing-docstring, too-few-public-methods, invalid-name
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ COPY nginx/default.conf ${TFW_NGINX_DEFAULT}
|
||||
COPY nginx/components/ ${TFW_NGINX_COMPONENTS}
|
||||
COPY lib LICENSE ${TFW_LIB_DIR}
|
||||
|
||||
RUN for dir in "${TFW_LIB_DIR}"/{tfw,tao,envvars.py} "/etc/nginx" "/etc/supervisor"; do \
|
||||
RUN for dir in "${TFW_LIB_DIR}"/{tfw,tao,envvars} "/etc/nginx" "/etc/supervisor"; do \
|
||||
chown -R root:root "$dir" && chmod -R 700 "$dir"; \
|
||||
done
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DirectoryMonitor(ObserverMixin):
|
||||
def __init__(self, directory):
|
||||
def __init__(self, directories):
|
||||
ObserverMixin.__init__(self)
|
||||
self.eventhandler = IdeReloadWatchdogEventHandler()
|
||||
for directory in directories:
|
||||
self.observer.schedule(self.eventhandler, directory, recursive=True)
|
||||
|
||||
self.pause, self.resume = self.eventhandler.pause, self.eventhandler.resume
|
||||
|
||||
@property
|
||||
|
||||
@@ -98,17 +98,24 @@ class IdeEventHandler(EventHandlerBase, MonitorManagerMixin):
|
||||
By default all files in the directory specified in __init__ are displayed
|
||||
on the fontend. Note that this is a stateful component.
|
||||
|
||||
When any file in the selected directory changes they are automatically refreshed
|
||||
on the frontend (this is done by listening to inotify events).
|
||||
|
||||
This EventHandler accepts messages that have a data["command"] key specifying
|
||||
a command to be executed.
|
||||
The API of each command is documented in their respective handlers.
|
||||
"""
|
||||
def __init__(self, key, directory, allowed_directories, selected_file=None, exclude=None):
|
||||
def __init__(self, key, directory, allowed_directories, selected_file=None, exclude=None,
|
||||
additional_watched_directories=None):
|
||||
"""
|
||||
:param key: the key this instance should listen to
|
||||
:param directory: working directory which the EventHandler should serve files from
|
||||
:param allowed_directories: list of directories that can be switched to using the selectdir command
|
||||
:param selected_file: file that is selected by default
|
||||
:param exclude: list of filenames that should not appear between files (for *.o, *.pyc, etc.)
|
||||
:param additional_watched_directories: refresh the selected file when files change in these directories
|
||||
(the working directory is watched by default, this is useful for
|
||||
symlinks and such)
|
||||
"""
|
||||
super().__init__(key)
|
||||
try:
|
||||
@@ -116,7 +123,11 @@ class IdeEventHandler(EventHandlerBase, MonitorManagerMixin):
|
||||
selected_file=selected_file, exclude=exclude)
|
||||
except IndexError:
|
||||
raise EnvironmentError(f'No file(s) in IdeEventHandler working_directory "{directory}"!')
|
||||
MonitorManagerMixin.__init__(self, DirectoryMonitor, self.filemanager.workdir)
|
||||
|
||||
self.watched_directories = [self.filemanager.workdir]
|
||||
if additional_watched_directories:
|
||||
self.watched_directories.extend(additional_watched_directories)
|
||||
MonitorManagerMixin.__init__(self, DirectoryMonitor, self.watched_directories)
|
||||
|
||||
self.commands = {'read': self.read,
|
||||
'write': self.write,
|
||||
|
||||
@@ -15,8 +15,7 @@ class ProcessManager(SupervisorMixin):
|
||||
def __init__(self):
|
||||
self.commands = {'start': self.start_process,
|
||||
'stop': self.stop_process,
|
||||
'restart': self.restart_process,
|
||||
'readlog': self.read_log}
|
||||
'restart': self.restart_process}
|
||||
|
||||
def __call__(self, command, process_name):
|
||||
return self.commands[command](process_name)
|
||||
@@ -46,10 +45,11 @@ class ProcessManagingEventHandler(EventHandlerBase):
|
||||
try:
|
||||
data = message['data']
|
||||
self.processmanager(data['command'], data['process_name'])
|
||||
message['data']['log'] = self.processmanager.read_log_stdout(message['data']['process_name'])
|
||||
return message
|
||||
except KeyError:
|
||||
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
|
||||
except SupervisorFault as fault:
|
||||
message['data']['error'] = fault.faultString
|
||||
message['data']['log'] = self.processmanager.read_log(message['data']['process_name'])
|
||||
message['data']['log'] = self.processmanager.read_log_stderr(message['data']['process_name'])
|
||||
return message
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
from abc import ABC
|
||||
from re import match
|
||||
from shlex import split
|
||||
|
||||
from tfw.config.logs import logging
|
||||
|
||||
@@ -57,10 +58,10 @@ class TerminalCommands(ABC):
|
||||
return match(self._command_method_regex, string)
|
||||
|
||||
def callback(self, history):
|
||||
parts = history[-1].split()
|
||||
parts = split(history[-1])
|
||||
command = parts[0]
|
||||
if command in self.command_implemetations.keys():
|
||||
try:
|
||||
self.command_implemetations[command](*parts[1:])
|
||||
except IndexError:
|
||||
LOG.debug('Command "%s" failed!', command)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
LOG.exception('Command "%s" failed:', command)
|
||||
|
||||
@@ -7,10 +7,10 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MonitorManagerMixin:
|
||||
def __init__(self, monitor_type, directory):
|
||||
def __init__(self, monitor_type, directories):
|
||||
self._monitor_type = monitor_type
|
||||
self._monitor = None
|
||||
self._monitored_directory = directory
|
||||
self._monitored_directories = directories
|
||||
self.reload_monitor()
|
||||
|
||||
@property
|
||||
@@ -23,5 +23,5 @@ class MonitorManagerMixin:
|
||||
self._monitor.stop()
|
||||
except KeyError:
|
||||
LOG.debug('Working directory was removed – ignoring...')
|
||||
self._monitor = self._monitor_type(self._monitored_directory)
|
||||
self._monitor = self._monitor_type(self._monitored_directories)
|
||||
self._monitor.watch() # This runs on a separate thread
|
||||
|
||||
@@ -19,11 +19,21 @@ class SupervisorMixin:
|
||||
def start_process(self, process_name):
|
||||
self.supervisor.startProcess(process_name)
|
||||
|
||||
def read_log(self, process_name):
|
||||
logs = self.supervisor.readProcessStderrLog(process_name, 0, 0)
|
||||
remove(self.supervisor.getProcessInfo(process_name)['stderr_logfile'])
|
||||
def read_log_stdout(self, process_name):
|
||||
return self._read_log_internal(self.supervisor.readProcessStdoutLog, process_name)
|
||||
|
||||
def read_log_stderr(self, process_name):
|
||||
return self._read_log_internal(self.supervisor.readProcessStderrLog, process_name)
|
||||
|
||||
def _read_log_internal(self, read_method, process_name):
|
||||
log = read_method(process_name, 0, 0)
|
||||
self.clear_logs(process_name)
|
||||
return log
|
||||
|
||||
def clear_logs(self, process_name):
|
||||
for logfile in ('stdout_logfile', 'stderr_logfile'):
|
||||
remove(self.supervisor.getProcessInfo(process_name)[logfile])
|
||||
self.supervisor.clearProcessLogs(process_name)
|
||||
return logs
|
||||
|
||||
def restart_process(self, process_name):
|
||||
self.stop_process(process_name)
|
||||
|
||||
@@ -35,6 +35,9 @@ class ServerUplinkConnector(ZMQConnectorBase):
|
||||
def send_to_eventhandler(self, message):
|
||||
"""
|
||||
Send a message to an event handler.
|
||||
This envelopes the desired message in the 'data' field of the message to
|
||||
TFWServer, which will mirror it to event handlers.
|
||||
|
||||
:param message: JSON message you want to send
|
||||
:param message['key']: key of event handler you want to address
|
||||
"""
|
||||
@@ -46,6 +49,7 @@ class ServerUplinkConnector(ZMQConnectorBase):
|
||||
def send(self, message):
|
||||
"""
|
||||
Send a message to the TFW server
|
||||
|
||||
:param message: JSON message you want to send
|
||||
"""
|
||||
self._zmq_push_socket.send_multipart(serialize_tfw_msg(message))
|
||||
|
||||
@@ -29,10 +29,16 @@ def validate_message(message):
|
||||
|
||||
|
||||
def serialize_tfw_msg(message):
|
||||
"""
|
||||
Create TFW multipart data from message dict
|
||||
"""
|
||||
return _serialize_all(message['key'], message)
|
||||
|
||||
|
||||
def deserialize_tfw_msg(*args):
|
||||
"""
|
||||
Return message from TFW multipart data
|
||||
"""
|
||||
return _deserialize_all(*args)[1]
|
||||
|
||||
|
||||
@@ -45,12 +51,20 @@ def _deserialize_all(*args):
|
||||
|
||||
|
||||
def _serialize_single(data):
|
||||
"""
|
||||
Return input as bytes
|
||||
(serialize input if it is JSON)
|
||||
"""
|
||||
if not isinstance(data, str):
|
||||
data = json.dumps(data)
|
||||
return _encode_if_needed(data)
|
||||
|
||||
|
||||
def _deserialize_single(data):
|
||||
"""
|
||||
Try parsing input as JSON, return it as
|
||||
string if parsing fails.
|
||||
"""
|
||||
try:
|
||||
return json.loads(data)
|
||||
except ValueError:
|
||||
@@ -58,12 +72,20 @@ def _deserialize_single(data):
|
||||
|
||||
|
||||
def _encode_if_needed(value):
|
||||
"""
|
||||
Return input as bytes
|
||||
(encode if input is string)
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
value = value.encode('utf-8')
|
||||
return value
|
||||
|
||||
|
||||
def _decode_if_needed(value):
|
||||
"""
|
||||
Return input as string
|
||||
(decode if input is bytes)
|
||||
"""
|
||||
if isinstance(value, (bytes, bytearray)):
|
||||
value = value.decode('utf-8')
|
||||
return value
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from os.path import dirname, realpath, join
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
here = dirname(realpath(__file__))
|
||||
|
||||
with open(join(here, 'VERSION'), 'r') as ifile:
|
||||
version = ifile.read().strip('\n')
|
||||
with open(join(here, 'requirements.txt'), 'r') as ifile:
|
||||
requirements = ifile.read().splitlines()
|
||||
|
||||
setup(name='tfw',
|
||||
version=version,
|
||||
description='Avatao tutorial-framework',
|
||||
url='https://github.com/avatao-content/baseimage-tutorial-framework',
|
||||
author='Avatao.com Innovative Learning Kft.',
|
||||
author_email='support@avatao.com',
|
||||
license='custom',
|
||||
packages=find_packages('lib'),
|
||||
package_dir={'': 'lib'},
|
||||
install_requires=requirements,
|
||||
zip_safe=False)
|
||||
Reference in New Issue
Block a user