From b98d93d78848019fea77474a8de897674da52057 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jul 06 2017 12:04:19 +0000 Subject: drop mod_python support Fixes: https://pagure.io/koji/issue/466 --- diff --git a/docs/source/server_howto.rst b/docs/source/server_howto.rst index f3786d2..50f2fbf 100644 --- a/docs/source/server_howto.rst +++ b/docs/source/server_howto.rst @@ -583,10 +583,6 @@ following: Koji Hub ======== -.. note:: - Koji 1.7 and greater uses mod_wsgi. Any mod_python configurations will - need to be migrated. - Koji-hub is the center of all Koji operations. It is an XML-RPC server running under mod_wsgi in the Apache httpd. koji-hub is passive in that it only receives XML-RPC calls and relies upon the build daemons and other components @@ -833,10 +829,6 @@ The following command will test your login to the hub: Koji Web - Interface for the Masses =================================== -.. note:: - Koji 1.7 and greater uses mod_wsgi. Any mod_python configurations will - need to be migrated. - Koji-web is a set of scripts that run in mod_wsgi and use the Cheetah templating engine to provide an web interface to Koji. koji-web exposes a lot of information and also provides a means for certain operations, such as @@ -874,11 +866,6 @@ The koji-web package provides this configuration file. You will need to modify it based on your authentication type. Instructions are contained within the file and should be simple to follow. -.. note:: - RHEL 5's mod_python publisher does not support non-basic auth, so Kerberos - authentication does not currently work in EPEL 5's Koji-web. See - `BZ #682319 `_. - /etc/httpd/conf.d/ssl.conf ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hub/httpd.conf b/hub/httpd.conf index a866b17..82cc217 100644 --- a/hub/httpd.conf +++ b/hub/httpd.conf @@ -19,17 +19,6 @@ Alias /kojihub /usr/share/koji-hub/kojixmlrpc.py -# Support for mod_python is DEPRECATED. If you still need mod_python support, -# then use the following directory settings instead: -# -# -# SetHandler mod_python -# PythonHandler kojixmlrpc -# PythonOption ConfigFile /etc/koji-hub/hub.conf -# PythonDebug Off -# PythonAutoReload Off -# - # Also serve /mnt/koji Alias /kojifiles "/mnt/koji/" diff --git a/hub/kojixmlrpc.py b/hub/kojixmlrpc.py index 6f848d8..a799ad5 100644 --- a/hub/kojixmlrpc.py +++ b/hub/kojixmlrpc.py @@ -31,7 +31,6 @@ import pprint import resource import xmlrpclib from xmlrpclib import getparser, dumps, Fault -from koji.server import WSGIWrapper import koji import koji.auth @@ -203,7 +202,7 @@ class HandlerAccess(object): class ModXMLRPCRequestHandler(object): - """Simple XML-RPC handler for mod_python environment""" + """Simple XML-RPC handler for mod_wsgi environment""" def __init__(self, handlers): self.traceback = False @@ -394,21 +393,8 @@ def load_config(environ): """ logger = logging.getLogger("koji") #get our config file(s) - if 'modpy.opts' in environ: - modpy_opts = environ.get('modpy.opts') - cf = modpy_opts.get('ConfigFile', None) - # to aid in the transition from PythonOptions to hub.conf, we only load - # the configfile if it is explicitly configured - if cf == '/etc/koji-hub/hub.conf': - cfdir = modpy_opts.get('ConfigDir', '/etc/koji-hub/hub.conf.d') - else: - cfdir = modpy_opts.get('ConfigDir', None) - if not cf and not cfdir: - logger.warn('Warning: configuring Koji via PythonOptions is deprecated. Use hub.conf') - else: - cf = environ.get('koji.hub.ConfigFile', '/etc/koji-hub/hub.conf') - cfdir = environ.get('koji.hub.ConfigDir', '/etc/koji-hub/hub.conf.d') - modpy_opts = {} + cf = environ.get('koji.hub.ConfigFile', '/etc/koji-hub/hub.conf') + cfdir = environ.get('koji.hub.ConfigDir', '/etc/koji-hub/hub.conf.d') if cfdir: configs = koji.config_directory_contents(cfdir) else: @@ -482,27 +468,16 @@ def load_config(environ): ] opts = {} for name, dtype, default in cfgmap: - if config: - key = ('hub', name) - if config.has_option(*key): - if dtype == 'integer': - opts[name] = config.getint(*key) - elif dtype == 'boolean': - opts[name] = config.getboolean(*key) - else: - opts[name] = config.get(*key) - else: - opts[name] = default - else: - if modpy_opts.get(name, None) is not None: - if dtype == 'integer': - opts[name] = int(modpy_opts.get(name)) - elif dtype == 'boolean': - opts[name] = modpy_opts.get(name).lower() in ('yes', 'on', 'true', '1') - else: - opts[name] = modpy_opts.get(name) + key = ('hub', name) + if config and config.has_option(*key): + if dtype == 'integer': + opts[name] = config.getint(*key) + elif dtype == 'boolean': + opts[name] = config.getboolean(*key) else: - opts[name] = default + opts[name] = config.get(*key) + continue + opts[name] = default if opts['DBHost'] is None: opts['DBHost'] = opts['DBhost'] # load policies @@ -679,15 +654,6 @@ def load_scripts(environ): import kojihub -# -# mod_python handler -# - -def handler(req): - wrapper = WSGIWrapper(req) - return wrapper.run(application) - - def get_memory_usage(): pagesize = resource.getpagesize() statm = [pagesize*int(y)/1024 for y in "".join(open("/proc/self/statm").readlines()).strip().split()] diff --git a/koji.next.md b/koji.next.md index 255359e..b2003ad 100644 --- a/koji.next.md +++ b/koji.next.md @@ -81,7 +81,6 @@ Warning to the reader: - streamlined cli options - marker files for many things on disk - more history data -- drop modpython support - policy code - more robust syntax - test negation diff --git a/koji/server.py b/koji/server.py index 6a6ae6a..78284aa 100644 --- a/koji/server.py +++ b/koji/server.py @@ -19,170 +19,8 @@ # Authors: # Mike McLean -import sys -import traceback -from koji.util import LazyDict - -try: - from mod_python import apache -except ImportError: # pragma: no cover - apache = None - - class ServerError(Exception): """Base class for our server-side-only exceptions""" class ServerRedirect(ServerError): """Used to handle redirects""" - - -class WSGIWrapper(object): - """A very thin wsgi compat layer for mod_python - - This class is highly specific to koji and is not fit for general use. - It does not support the full wsgi spec - """ - - def __init__(self, req): - self.req = req - self._env = None - host, port = req.connection.remote_addr - environ = { - 'REMOTE_ADDR': req.connection.remote_ip, - # or remote_addr[0]? - # or req.get_remote_host(apache.REMOTE_NOLOOKUP)? - 'REMOTE_PORT': str(req.connection.remote_addr[1]), - 'REMOTE_USER': req.user, - 'REQUEST_METHOD': req.method, - 'REQUEST_URI': req.uri, - 'PATH_INFO': req.path_info, - 'SCRIPT_FILENAME': req.filename, - 'QUERY_STRING': req.args or '', - 'SERVER_NAME': req.hostname, - 'SERVER_PORT': str(req.connection.local_addr[1]), - 'wsgi.version': (1, 0), - 'wsgi.input': InputWrapper(req), - 'wsgi.errors': sys.stderr, - #TODO - file_wrapper support - } - environ = LazyDict(environ) - environ.lazyset('wsgi.url_scheme', self.get_scheme, []) - environ.lazyset('modpy.env', self.env, []) - environ.lazyset('modpy.opts', req.get_options, []) - environ.lazyset('modpy.conf', req.get_config, []) - environ.lazyset('SCRIPT_NAME', self.script_name, [], cache=True) - env_keys = ['SSL_CLIENT_VERIFY', 'HTTPS', 'SSL_CLIENT_S_DN'] - for key in env_keys: - environ.lazyset(key, self.envget, [key]) - # The component of the DN used for the username is usually the CN, - # but it is configurable. - # Allow retrieval of some common DN components from the environment. - for comp in ['C', 'ST', 'L', 'O', 'OU', 'CN', 'Email']: - key = 'SSL_CLIENT_S_DN_' + comp - environ.lazyset(key, self.envget, [key]) - #gather the headers we care about - for key in req.headers_in: - k2 = key.upper() - k2 = k2.replace('-', '_') - if k2 not in ['CONTENT_TYPE', 'CONTENT_LENGTH']: - k2 = 'HTTP_' + k2 - environ[k2] = req.headers_in[key] - self.environ = environ - self.set_headers = False - - def env(self): - if self._env is None: - self.req.add_common_vars() - self._env = self.req.subprocess_env - return self._env - - def envget(self, *args): - return self.env().get(*args) - - def script_name(self): - uri = self.req.uri - path_info = self.req.path_info - if uri.endswith(path_info): - uri = uri[:-len(path_info)] - uri = uri.rstrip('/') - return uri - - def get_scheme(self): - if self.envget('HTTPS') in ('yes', 'on', '1'): - return 'https' - else: - return 'http' - - def no_write(self, string): - """a fake write() callable returned by start_response - - we don't use the write() callable in koji, so it will raise an error if called - """ - raise RuntimeError("wsgi write() callable not supported") - - def start_response(self, status, headers, exc_info=None): - #XXX we don't deal with exc_info - if self.set_headers: - raise RuntimeError("start_response() already called") - self.req.status = int(status[:3]) - for key, val in headers: - if key.lower() == 'content-length': - self.req.set_content_length(int(val)) - elif key.lower() == 'content-type': - self.req.content_type = val - else: - self.req.headers_out.add(key, val) - self.set_headers = True - return self.no_write - - def run(self, handler): - try: - result = handler(self.environ, self.start_response) - self.write_result(result) - return apache.OK - except: - sys.stderr.write(''.join(traceback.format_exception(*sys.exc_info()))) - sys.stderr.flush() - raise apache.SERVER_RETURN(apache.HTTP_INTERNAL_SERVER_ERROR) - - def write_result(self, result): - """called by run() to handle the application's result value""" - req = self.req - write = req.write - if self.set_headers: - for chunk in result: - write(chunk) - else: - #slower version -- need to check for set_headers - for chunk in result: - if chunk and not self.set_headers: - raise RuntimeError("write() called before start_response()") - write(chunk) - if not req.bytes_sent: - #application sent nothing back - req.set_content_length(0) - - - -class InputWrapper(object): - - def __init__(self, req): - self.req = req - - def close(self): - pass - - def read(self, size=-1): - return self.req.read(size) - - def readline(self): - return self.req.readline() - - def readlines(self, hint=-1): - return self.req.readlines(hint) - - def __iter__(self): - line = self.readline() - while line: - yield line - line = self.readline() diff --git a/www/conf/kojiweb.conf b/www/conf/kojiweb.conf index 807ef23..020fbc5 100644 --- a/www/conf/kojiweb.conf +++ b/www/conf/kojiweb.conf @@ -17,22 +17,6 @@ Alias /koji "/usr/share/koji-web/scripts/wsgi_publisher.py" -# Support for mod_python is DEPRECATED. If you still need mod_python support, -# then use the following directory settings instead: -# -# -# # Config for the publisher handler -# SetHandler mod_python -# # Use kojiweb's publisher (provides wsgi compat layer) -# # mod_python's publisher is no longer supported -# PythonHandler wsgi_publisher -# PythonOption koji.web.ConfigFile /etc/kojiweb/web.conf -# PythonAutoReload Off -# # Configuration via PythonOptions is DEPRECATED. Use /etc/kojiweb/web.conf -# Order allow,deny -# Allow from all -# - # uncomment this to enable authentication via Kerberos # # AuthType Kerberos diff --git a/www/kojiweb/wsgi_publisher.py b/www/kojiweb/wsgi_publisher.py index e2d427b..c69559e 100644 --- a/www/kojiweb/wsgi_publisher.py +++ b/www/kojiweb/wsgi_publisher.py @@ -30,7 +30,7 @@ import sys import traceback from ConfigParser import RawConfigParser -from koji.server import WSGIWrapper, ServerError, ServerRedirect +from koji.server import ServerError, ServerRedirect from koji.util import dslice @@ -122,17 +122,8 @@ class Dispatcher(object): - all PythonOptions (except koji.web.ConfigFile) are now deprecated and support for them will disappear in a future version of Koji """ - modpy_opts = environ.get('modpy.opts', {}) - if 'modpy.opts' in environ: - cf = modpy_opts.get('koji.web.ConfigFile', None) - cfdir = modpy_opts.get('koji.web.ConfigDir', None) - # to aid in the transition from PythonOptions to web.conf, we do - # not check the config file by default, it must be configured - if not cf and not cfdir: - self.logger.warn('Warning: configuring Koji via PythonOptions is deprecated. Use web.conf') - else: - cf = environ.get('koji.web.ConfigFile', '/etc/kojiweb/web.conf') - cfdir = environ.get('koji.web.ConfigDir', '/etc/kojiweb/web.conf.d') + cf = environ.get('koji.web.ConfigFile', '/etc/kojiweb/web.conf') + cfdir = environ.get('koji.web.ConfigDir', '/etc/kojiweb/web.conf.d') if cfdir: configs = koji.config_directory_contents(cfdir) else: @@ -142,40 +133,23 @@ class Dispatcher(object): if configs: config = RawConfigParser() config.read(configs) - elif modpy_opts: - # presumably we are configured by modpy options - config = None else: raise koji.GenericError("Configuration missing") opts = {} for name, dtype, default in self.cfgmap: - if config: - key = ('web', name) - if config.has_option(*key): - if dtype == 'integer': - opts[name] = config.getint(*key) - elif dtype == 'boolean': - opts[name] = config.getboolean(*key) - elif dtype == 'list': - opts[name] = [x.strip() for x in config.get(*key).split(',')] - else: - opts[name] = config.get(*key) + key = ('web', name) + if config and config.has_option(*key): + if dtype == 'integer': + opts[name] = config.getint(*key) + elif dtype == 'boolean': + opts[name] = config.getboolean(*key) + elif dtype == 'list': + opts[name] = [x.strip() for x in config.get(*key).split(',')] else: - opts[name] = default + opts[name] = config.get(*key) else: - if modpy_opts.get(name, None) is not None: - if dtype == 'integer': - opts[name] = int(modpy_opts.get(name)) - elif dtype == 'boolean': - opts[name] = modpy_opts.get(name).lower() in ('yes', 'on', 'true', '1') - else: - opts[name] = modpy_opts.get(name) - else: - opts[name] = default - if 'modpy.conf' in environ: - debug = environ['modpy.conf'].get('PythonDebug', '0').lower() - opts['PythonDebug'] = (debug in ['yes', 'on', 'true', '1']) + opts[name] = default opts['Secret'] = koji.util.HiddenValue(opts['Secret']) self.options = opts return opts @@ -432,11 +406,6 @@ class Dispatcher(object): result = [result] return result - def handler(self, req): - """mod_python handler""" - wrapper = WSGIWrapper(req) - return wrapper.run(self.application) - def application(self, environ, start_response): """wsgi handler""" if self.formatter: @@ -483,7 +452,6 @@ class HubFormatter(logging.Formatter): return logging.Formatter.format(self, record) -# provide necessary global handlers for mod_wsgi and mod_python +# provide necessary global handlers for mod_wsgi dispatcher = Dispatcher() -handler = dispatcher.handler application = dispatcher.application