import logging from ast import literal_eval from functools import partial from signal import signal, SIGTERM, SIGINT from tornado.ioloop import IOLoop from tfw.fsm import YamlFSM from tfw.event_handlers import EventHandlerBase, FSMAwareEventHandler, TFWServerUplinkConnector from tfw.components import IdeEventHandler, TerminalEventHandler from tfw.components import ProcessManagingEventHandler, BashMonitor from tfw.components import TerminalCommands, LogMonitoringEventHandler from tfw.components import FSMManagingEventHandler, DirectorySnapshottingEventHandler from tfw.components import FrontendEventHandler, MessageSender from tfw.config import TFWENV from tao.config import TAOENV from tfw.config.log import TFWLog LOG = logging.getLogger(__name__) class TerminalCallbackEventHandler(EventHandlerBase): """ Logs commands executed in terminal to messages and invokes an additional callback function to handle special commands. !! Please remove from production code. !! """ def __init__(self, key, callback): self.callback = callback super().__init__(key) def handle_event(self, message): command = message['value'] self.cenator(command) self.callback(command) @staticmethod def cenator(command): LOG.debug('User executed command: "%s"', command) MessageSender().send('JOHN CENA', f'You\'ve executed "{command}"') class TestCommands(TerminalCommands): """ Some example commands useful for debugging. !! Please remove from production code !! and inherit your own class from TerminalCommands if you need to define custom commands in your challenge. """ # pylint: disable=unused-argument, attribute-defined-outside-init, no-self-use def command_sendmessage(self, *args): """ Insert TFW message template as first argument if executed without args. Evaluate first argumen as a dict and send it to the frontend. This is useful for playing around with frontend APIs. """ if not args: message_template = """'{"key": "", "data": {"command": ""}}'""" TFWServerUplinkConnector().send_message({ 'key': 'shell', 'data': { 'command': 'write', 'value': f'sendmessage {message_template}' } }) else: TFWServerUplinkConnector().send_message(literal_eval(args[0])) def command_seppuku_tfw(self, *args): """ Restart tfw_server.py and event_handler_main.py. This can speed up development when combined with mounting volumes from host to container. """ seppuku = ( 'nohup sh -c "supervisorctl restart tfwserver event_handler_main" &> /dev/null & ' 'clear && echo "Committed seppuku! :)" && sleep infinity' ) uplink = TFWServerUplinkConnector() uplink.send_message({ 'key': 'shell', 'data': { 'command': 'write', 'value': f'{seppuku}\n' } }) uplink.send_message({ 'key': 'dashboard', 'data': { 'command': 'reloadFrontend' } }) class MessageFSMStepsEventHandler(FSMAwareEventHandler): """ This example EventHandler is capable of detecting FSM state. !! Please remove from production code !! """ def handle_event(self, message): pass def handle_fsm_step(self, **kwargs): """ When the FSM steps this method is invoked. Receives a 'data' field from an fsm_update message as kwargs. """ MessageSender().send( 'FSM info', f'FSM has stepped from state "{kwargs["last_event"]["from_state"]}" ' f'to state "{kwargs["current_state"]}" in response to trigger "{kwargs["last_event"]["trigger"]}"' ) def main(): # pylint: disable=unused-variable TFWLog().start() # TFW component EventHandlers (builtins, required for their respective functionalities) fsm = FSMManagingEventHandler( # TFW FSM key='fsm', fsm_type=partial( YamlFSM, 'test_fsm.yml', {} # jinja2 variables, use empty dict to enable jinja2 parsing without any variables ) ) ide = IdeEventHandler( # Web IDE backend key='ide', allowed_directories=[TFWENV.IDE_WD, TFWENV.WEBSERVICE_DIR], directory=TFWENV.IDE_WD, exclude=['*.pyc'] ) terminal = TerminalEventHandler( # Web shell backend key='shell', monitor=BashMonitor(TFWENV.HISTFILE) ) commands = TerminalCallbackEventHandler( # Reacts to terminal commands 'history.bash', TestCommands(bashrc=f'/home/{TAOENV.USER}/.bashrc').callback ) processmanager = ProcessManagingEventHandler( # Handles 'deploy' button clicks key='processmanager', dirmonitor=ide.monitor, log_tail=2000 ) logmonitor = LogMonitoringEventHandler( # Sends live logs of webservice process to frontend key='logmonitor', process_name='webservice', log_tail=2000 ) snapshot = DirectorySnapshottingEventHandler( # Manages filesystem snapshots of directories key='snapshot', directories=[ TFWENV.IDE_WD, TFWENV.WEBSERVICE_DIR ] ) frontend = FrontendEventHandler() # Proxies frontend API calls to frontend # Your custom event handlers message_fsm_steps_eh = MessageFSMStepsEventHandler( key='test' ) event_handlers = EventHandlerBase.get_local_instances() def stop(sig, frame): for eh in event_handlers: eh.stop() exit(0) signal(SIGTERM, stop) signal(SIGINT, stop) IOLoop.instance().start() if __name__ == '__main__': main()