From c2d9a373c1dbbb4270c0ff75bae877b915500fce Mon Sep 17 00:00:00 2001 From: Yves-Gwenael Bourhis Date: Mon, 24 Mar 2014 11:39:28 +0100 Subject: [PATCH] Created a make_web_conf command. The current wsgi does not support virtualenvs (in some distributions, the dependencies may conflict with the distribution's python packages/dependencies and would require a virtualenv even in production). Also, making an apache web configuration is not always trivial especialy with ssl. This "make_web_conf" command creates a wsgi with automatic virtualenvironment detection (if there is a virtualenvironment), and creates an apache (normal or ssl) configuration TODO(ygbo):: - add nginx support to generate nginx configuration files. - add gunicorn support. - add uwsgi support. implements bp web-conf-generation-script Change-Id: I6397ba01df88b540bbdca4bf21ba90be6843022a --- .gitignore | 1 + openstack_dashboard/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../commands/apache_vhost.conf.template | 37 +++ .../management/commands/horizon.wsgi.template | 13 + .../management/commands/make_web_conf.py | 244 ++++++++++++++++++ 6 files changed, 295 insertions(+) create mode 100644 openstack_dashboard/management/__init__.py create mode 100644 openstack_dashboard/management/commands/__init__.py create mode 100644 openstack_dashboard/management/commands/apache_vhost.conf.template create mode 100644 openstack_dashboard/management/commands/horizon.wsgi.template create mode 100644 openstack_dashboard/management/commands/make_web_conf.py diff --git a/.gitignore b/.gitignore index d0976ebfba..cb0d723375 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ horizon.egg-info openstack_dashboard/local/local_settings.py openstack_dashboard/local/.secret_key_store openstack_dashboard/test/.secret_key_store +openstack_dashboard/wsgi/horizon.wsgi doc/build/ doc/source/sourcecode /static/ diff --git a/openstack_dashboard/management/__init__.py b/openstack_dashboard/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/management/commands/__init__.py b/openstack_dashboard/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/management/commands/apache_vhost.conf.template b/openstack_dashboard/management/commands/apache_vhost.conf.template new file mode 100644 index 0000000000..5db9318527 --- /dev/null +++ b/openstack_dashboard/management/commands/apache_vhost.conf.template @@ -0,0 +1,37 @@ +{% if SSL %} +# Set "NameVirtualHost {{ NAMEDHOST }}:443" in your httpd.conf file if it's not already done. + + SSLEngine on + SSLCertificateFile {{ SSLCERT }} + SSLCertificateKeyFile {{ SSLKEY }} +{% if CACERT %} SSLCACertificateFile {{ CACERT }}{% endif %} +{% else %} + +{% endif %} + ServerAdmin {{ ADMIN }} + ServerName {{ VHOSTNAME }} + + DocumentRoot {{ PROJECT_ROOT }}/ + + LogLevel warn + ErrorLog {{ LOGDIR }}/{{ PROJECT_NAME }}-error.log + CustomLog {{ LOGDIR }}/{{ PROJECT_NAME }}-access.log combined + + WSGIScriptReloading On + WSGIDaemonProcess {{ PROJECT_NAME }}_website + WSGIProcessGroup {{ PROJECT_NAME }}_website + WSGIApplicationGroup {{ PROJECT_NAME }}_website + WSGIPassAuthorization On + + WSGIScriptAlias / {{ WSGI_FILE }} + + + Order Allow,Deny + Allow from all + + + Alias /static {{ STATIC_PATH }} + + SetHandler None + + diff --git a/openstack_dashboard/management/commands/horizon.wsgi.template b/openstack_dashboard/management/commands/horizon.wsgi.template new file mode 100644 index 0000000000..3832a2446f --- /dev/null +++ b/openstack_dashboard/management/commands/horizon.wsgi.template @@ -0,0 +1,13 @@ +#!/usr/bin/env python +import os +import sys +{% if ACTIVATE_THIS %} + +activate_this = '{{ ACTIVATE_THIS }}' +execfile(activate_this, dict(__file__=activate_this)) +{% endif %} +sys.path.insert(0, '{{ PROJECT_ROOT }}') +os.environ['DJANGO_SETTINGS_MODULE'] = '{{ DJANGO_SETTINGS_MODULE }}' + +import django.core.handlers.wsgi +application = django.core.handlers.wsgi.WSGIHandler() diff --git a/openstack_dashboard/management/commands/make_web_conf.py b/openstack_dashboard/management/commands/make_web_conf.py new file mode 100644 index 0000000000..1d8f2577eb --- /dev/null +++ b/openstack_dashboard/management/commands/make_web_conf.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import print_function + +from optparse import make_option # noqa +import os +import socket +import sys +import warnings + +from django.conf import settings +from django.core.management.base import BaseCommand # noqa +from django.template import Context, Template # noqa + +# Suppress DeprecationWarnings which clutter the output to the point of +# rendering it unreadable. +warnings.simplefilter('ignore') + +cmd_name = __name__.split('.')[-1] + +CURDIR = os.path.realpath(os.path.dirname(__file__)) +PROJECT_PATH = os.path.realpath(os.path.join(CURDIR, '../..')) +STATIC_PATH = os.path.realpath(os.path.join(PROJECT_PATH, '../static')) + +# Known apache log directory locations +APACHE_LOG_DIRS = ( + '/var/log/httpd', # RHEL / Red Hat / CentOS / Fedora Linux + '/var/log/apache2', # Debian / Ubuntu Linux +) +# Default log directory +DEFAULT_LOG_DIR = '/var/log' + + +def _getattr(obj, name, default): + """Like getattr but return `default` if None or False. + + By default, getattr(obj, name, default) returns default only if + attr does not exist, here, we return `default` even if attr evaluates to + None or False. + """ + value = getattr(obj, name, default) + if value: + return value + else: + return default + + +context = Context({ + 'DJANGO_SETTINGS_MODULE': os.environ['DJANGO_SETTINGS_MODULE'], + 'HOSTNAME': socket.getfqdn(), + 'PROJECT_PATH': os.path.realpath( + _getattr(settings, 'ROOT_PATH', PROJECT_PATH)), + 'STATIC_PATH': os.path.realpath( + _getattr(settings, 'STATIC_ROOT', STATIC_PATH)), + 'SSLCERT': '/etc/pki/tls/certs/ca.crt', + 'SSLKEY': '/etc/pki/tls/private/ca.key', + 'CACERT': None, +}) + +context['PROJECT_ROOT'] = os.path.dirname(context['PROJECT_PATH']) +context['PROJECT_DIR_NAME'] = os.path.basename( + context['PROJECT_PATH'].split(context['PROJECT_ROOT'])[1]) +context['PROJECT_NAME'] = context['PROJECT_DIR_NAME'] + +context['WSGI_FILE'] = os.path.join( + context['PROJECT_PATH'], 'wsgi/horizon.wsgi') + +VHOSTNAME = context['HOSTNAME'].split('.') +VHOSTNAME[0] = context['PROJECT_NAME'] +context['VHOSTNAME'] = '.'.join(VHOSTNAME) + +if len(VHOSTNAME) > 1: + context['DOMAINNAME'] = '.'.join(VHOSTNAME[1:]) +else: + context['DOMAINNAME'] = 'openstack.org' +context['ADMIN'] = 'webmaster@%s' % context['DOMAINNAME'] + +context['ACTIVATE_THIS'] = None +virtualenv = os.environ.get('VIRTUAL_ENV') +if virtualenv: + activate_this = os.path.join( + virtualenv, 'bin/activate_this.py') + if os.path.exists(activate_this): + context['ACTIVATE_THIS'] = activate_this + + +def find_apache_log_dir(): + for log_dir in APACHE_LOG_DIRS: + if os.path.exists(log_dir) and os.path.isdir(log_dir): + return log_dir + return DEFAULT_LOG_DIR +context['LOGDIR'] = find_apache_log_dir() + + +class Command(BaseCommand): + + args = '' + help = """Create %(wsgi_file)s +or the contents of an apache %(p_name)s.conf file (on stdout). +The apache configuration is generated on stdout because the place of this +file is distribution dependent. + +examples:: + + manage.py %(cmd_name)s --wsgi # creates %(wsgi_file)s + manage.py %(cmd_name)s --apache # creates an apache vhost conf file (on \ +stdout). + manage.py %(cmd_name)s --apache --ssl --mail=%(admin)s \ +--project=%(p_name)s --hostname=%(hostname)s + +To create an acpache configuration file, redirect the output towards the +location you desire, e.g.:: + + manage.py %(cmd_name)s --apache > \ +/etc/httpd/conf.d/openstack_dashboard.conf + + """ % { + 'cmd_name': cmd_name, + 'p_name': context['PROJECT_NAME'], + 'wsgi_file': context['WSGI_FILE'], + 'admin': context['ADMIN'], + 'hostname': context['VHOSTNAME'], } + + option_list = BaseCommand.option_list + ( + # TODO(ygbo): Add an --nginx option. + make_option("-a", "--apache", + default=False, action="store_true", dest="apache", + help="generate an apache vhost configuration"), + make_option("--cacert", + dest="cacert", + help=("Use with the --apache and --ssl option to define " + "the path to the SSLCACertificateFile" + ), + metavar="CACERT"), + make_option("-f", "--force", + default=False, action="store_true", dest="force", + help="force overwriting of an existing %s file" % + context['WSGI_FILE']), + make_option("-H", "--hostname", + dest="hostname", + help=("Use with the --apache option to define the server's" + " hostname (default : %s)") % context['VHOSTNAME'], + metavar="HOSTNAME"), + make_option("--logdir", + dest="logdir", + help=("Use with the --apache option to define the path to " + "the apache log directory(default : %s)" + % context['LOGDIR']), + metavar="CACERT"), + make_option("-m", "--mail", + dest="mail", + help=("Use with the --apache option to define the web site" + " administrator's email (default : %s)") % + context['ADMIN'], + metavar="MAIL"), + make_option("-n", "--namedhost", + default=False, action="store_true", dest="namedhost", + help=("Use with the --apache option. The apache vhost " + "configuration will work only when accessed with " + "the proper hostname (see --hostname).")), + make_option("-p", "--project", + dest="project", + help=("Use with the --apache option to define the project " + "name (default : %s)") % context['PROJECT_NAME'], + metavar="PROJECT"), + make_option("-s", "--ssl", + default=False, action="store_true", dest="ssl", + help=("Use with the --apache option. The apache vhost " + "configuration will use an SSL configuration")), + make_option("--sslcert", + dest="sslcert", + help=("Use with the --apache and --ssl option to define " + "the path to the SSLCertificateFile (default : %s)" + ) % context['SSLCERT'], + metavar="SSLCERT"), + make_option("--sslkey", + dest="sslkey", + help=("Use with the --apache and --ssl option to define " + "the path to the SSLCertificateKeyFile " + "(default : %s)") % context['SSLKEY'], + metavar="SSLKEY"), + make_option("-w", "--wsgi", + default=False, action="store_true", dest="wsgi", + help="generate the horizon.wsgi file"), + ) + + def handle(self, *args, **options): + force = options.get('force') + context['SSL'] = options.get('ssl') + + if options.get('mail'): + context['ADMIN'] = options['mail'] + if options.get('cacert'): + context['CACERT'] = options['cacert'] + if options.get('logdir'): + context['LOGDIR'] = options['logdir'].rstrip('/') + if options.get('project'): + context['PROJECT_NAME'] = options['project'] + if options.get('hostname'): + context['VHOSTNAME'] = options['hostname'] + if options.get('sslcert'): + context['SSLCERT'] = options['sslcert'] + if options.get('sslkey'): + context['SSLKEY'] = options['sslkey'] + + if options.get('namedhost'): + context['NAMEDHOST'] = context['VHOSTNAME'] + else: + context['NAMEDHOST'] = '*' + + # Generate the WSGI. + if options.get('wsgi'): + with open( + os.path.join(CURDIR, 'horizon.wsgi.template'), 'r' + ) as fp: + wsgi_template = Template(fp.read()) + if not os.path.exists(context['WSGI_FILE']) or force: + with open(context['WSGI_FILE'], 'w') as fp: + fp.write(wsgi_template.render(context)) + print('Generated "%s"' % context['WSGI_FILE']) + else: + sys.exit('"%s" already exists, use --force to overwrite' % + context['WSGI_FILE']) + + # Generate the apache configuration. + elif options.get('apache'): + with open( + os.path.join(CURDIR, 'apache_vhost.conf.template'), 'r' + ) as fp: + wsgi_template = Template(fp.read()) + sys.stdout.write(wsgi_template.render(context)) + else: + self.print_help('manage.py', cmd_name)