You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
scriptform/src/scriptform.py

224 lines
8.0 KiB

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Main ScriptForm program
"""
import sys
if hasattr(sys, 'dont_write_bytecode'):
sys.dont_write_bytecode = True
import optparse
import os
import json
import logging
import thread
import hashlib
import socket
from daemon import Daemon
from formdefinition import FormDefinition
from formconfig import FormConfig
from webserver import ThreadedHTTPServer
from webapp import ScriptFormWebApp
class ScriptForm(object):
"""
'Main' class that orchestrates parsing the Form configurations and running
the webserver.
"""
def __init__(self, config_file, cache=True):
self.config_file = config_file
self.cache = cache
self.log = logging.getLogger('SCRIPTFORM')
self.form_config_singleton = None
self.websrv = None
self.running = False
self.httpd = None
# Init form config so it can raise errors about problems.
self.get_form_config()
def get_form_config(self):
"""
Read and return the form configuration in the form of a FormConfig
instance. If it has already been read, a cached version is returned.
"""
# Cache
if self.cache and self.form_config_singleton is not None:
return self.form_config_singleton
file_contents = file(self.config_file, 'r').read()
try:
config = json.loads(file_contents)
except ValueError as err:
sys.stderr.write("Error in form configuration '{}': {}\n".format(
self.config_file, err))
sys.exit(1)
static_dir = None
custom_css = None
users = None
forms = []
if 'static_dir' in config:
static_dir = config['static_dir']
if 'custom_css' in config:
custom_css = file(config['custom_css'], 'r').read()
if 'users' in config:
users = config['users']
for form in config['forms']:
form_name = form['name']
if not form['script'].startswith('/'):
# Script is relative to the current dir
script = os.path.join(os.path.realpath(os.curdir),
form['script'])
else:
# Absolute path to the script
script = form['script']
forms.append(
FormDefinition(form_name,
form['title'],
form['description'],
form['fields'],
script,
default_value=form.get('default_value', ""),
output=form.get('output', 'escaped'),
hidden=form.get('hidden', False),
submit_title=form.get('submit_title', 'Submit'),
allowed_users=form.get('allowed_users', None),
run_as=form.get('run_as', None))
)
form_config = FormConfig(
config['title'],
forms,
users,
static_dir,
custom_css
)
self.form_config_singleton = form_config
return form_config
def run(self, listen_addr='0.0.0.0', listen_port=8081):
"""
Start the webserver on address `listen_addr` and port `listen_port`.
This call is blocking until the user hits Ctrl-c, the shutdown() method
is called or something like SystemExit is raised in a handler.
"""
ScriptFormWebApp.scriptform = self
self.httpd = ThreadedHTTPServer((listen_addr, listen_port),
ScriptFormWebApp)
self.httpd.daemon_threads = True
self.log.info("Listening on %s:%s", listen_addr, listen_port)
self.running = True
self.httpd.serve_forever()
self.running = False
def shutdown(self):
"""
Shutdown the server. This interupts the run() method and must thus be
run in a seperate thread.
"""
self.log.info("Attempting server shutdown")
def t_shutdown(scriptform_instance):
"""
Callback for when the server is shutdown.
"""
scriptform_instance.log.info(self.websrv)
# Undocumented feature to shutdow the server.
scriptform_instance.httpd.socket.close()
scriptform_instance.httpd.shutdown()
# We need to spawn a new thread in which the server is shut down,
# because doing it from the main thread blocks, since the server is
# waiting for connections..
thread.start_new_thread(t_shutdown, (self, ))
def main(): # pragma: no cover
"""
main method
"""
usage = [
sys.argv[0] + " [option] (--start|--stop) <form_definition.json>",
" " + sys.argv[0] + " --generate-pw",
]
parser = optparse.OptionParser(version="%%VERSION%%")
parser.set_usage('\n'.join(usage))
parser.add_option("-g", "--generate-pw", dest="generate_pw",
action="store_true", default=False,
help="Generate password")
parser.add_option("-p", "--port", dest="port", action="store", type="int",
default=8081, help="Port to listen on (default=8081)")
parser.add_option("-f", "--foreground", dest="foreground",
action="store_true", default=False,
help="Run in foreground (debugging)")
parser.add_option("-r", "--reload", dest="reload", action="store_true",
default=False,
help="Reload form config on every request (DEV)")
parser.add_option("--pid-file", dest="pid_file", action="store",
default=None, help="Pid file")
parser.add_option("--log-file", dest="log_file", action="store",
default=None, help="Log file")
parser.add_option("--start", dest="action_start", action="store_true",
default=None, help="Start daemon")
parser.add_option("--stop", dest="action_stop", action="store_true",
default=None, help="Stop daemon")
(options, args) = parser.parse_args()
if options.generate_pw:
# Generate a password for use in the `users` section
import getpass
plain_pw = getpass.getpass()
if plain_pw != getpass.getpass('Repeat password: '):
sys.stderr.write("Passwords do not match.\n")
sys.exit(1)
sys.stdout.write(hashlib.sha256(plain_pw).hexdigest() + '\n')
sys.exit(0)
else:
if not options.action_stop and len(args) < 1:
parser.error("Insufficient number of arguments")
if not options.action_stop and not options.action_start:
options.action_start = True
# If a form configuration was specified, change to that dir so we can
# find the job scripts and such.
if args:
path = os.path.dirname(args[0])
if path:
os.chdir(path)
args[0] = os.path.basename(args[0])
daemon = Daemon(options.pid_file, options.log_file,
foreground=options.foreground)
log = logging.getLogger('MAIN')
try:
if options.action_start:
cache = not options.reload
scriptform_instance = ScriptForm(args[0], cache=cache)
daemon.register_shutdown_callback(scriptform_instance.shutdown)
daemon.start()
scriptform_instance.run(listen_port=options.port)
elif options.action_stop:
daemon.stop()
sys.exit(0)
except socket.error as err:
log.exception(err)
sys.stderr.write("Cannot bind to port {0}: {1}\n".format(
options.port,
str(err)
))
sys.exit(2)
except Exception as err:
log.exception(err)
raise
if __name__ == "__main__": # pragma: no cover
main()