~joaopinto/apt-portal/pre-modules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#   (C) Copyright 2009, APT-Portal Developers
#    https://launchpad.net/~apt-portal-devs
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# This is the main server setup code
import cherrypy
import sys
import os
import tempfile
from cherrypy import _cplogging
from elixir import *
from optparse import OptionParser
from cherrypy.lib import cptools
from logging import handlers, DEBUG
from common import sqlalchemy_tool
from common import precontroller
from common.controllers.webroot import Root, RootForce

 
"""
	Set the access logs to be rotated when they reach rot_maxBytes
	keeping a max of rot_backupCount log files
"""
def set_rotating_logs(app, appname, \
		rot_maxBytes = 10000000, rot_backupCount = 1000):
    """ Set rotated logs for app """
    log = app.log
    
    # Create the application logs directory
    logs_dir = "logs/%s" % appname
    if not os.path.exists(logs_dir):
        os.makedirs(logs_dir, 0700)

    # Remove the default FileHandlers if present.
    log.error_file = ""
    log.access_file = ""

    maxBytes = getattr(log, "rot_maxBytes", rot_maxBytes)    
    backupCount = getattr(log, "rot_backupCount", rot_backupCount)

    # Make a new RotatingFileHandler for the error log.
    fname = getattr(log, "rot_error_file", "%s/error.log" % logs_dir)
    h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
    h.setLevel(DEBUG)
    h.setFormatter(_cplogging.logfmt)
    log.error_log.addHandler(h)

    # Make a new RotatingFileHandler for the access log.
    fname = getattr(log, "rot_access_file", "%s/access.log" % logs_dir)
    h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
    h.setLevel(DEBUG)
    h.setFormatter(_cplogging.logfmt)
    log.access_log.addHandler(h)
 
"""
	Set cherrypy server configuration 
"""  
def set_cherrypy_config(appname, db_url, echo):
	""" Set the cherrypy web server configuration """
	current_dir = os.path.dirname(os.path.abspath(__file__))

	aptportal_threads = int(os.environ.get('APTPORTAL_THREADS', 100))
	
	# Set network related options and optional console log
	cherrypy.config.update({'environment': 'production',
							'server.socket_host': options.host,
							'server.socket_port': int(options.port),
							'server.thread_pool': aptportal_threads,
							'log.screen': options.console_log})
							
	# Enable gzip compression
	cherrypy.config.update({'tools.gzip.on': 'True'})
		
	# Enable sessions support
	if not os.path.isdir('/tmp/cherrypy_sessions_' + appname):
		os.mkdir('/tmp/cherrypy_sessions_' + appname, 0700)	
	cherrypy.config.update({'tools.sessions.on': True \
		, 'tools.sessions.storage_type': "file" \
		, 'tools.sessions.storage_path': "/tmp/cherrypy_sessions_"  + appname \
		, 'tools.sessions.timeout': 60 \
		
	})

	# Set utf8 encoding options
	cherrypy.config.update({'tools.encode.on':True \
		, 'tools.encode.encoding':'utf-8' \
		, 'tools.encode.errors':'replace' \
	})
	
	# Setup SQLAlchemy transaction handler
	cherrypy.config.update({'tools.SATransaction.on' : True \
		, 'tools.SATransaction.dburi' : db_url \
		, 'tools.SATransaction.echo': echo,
	})
	
	# Enable the precontroller tool
	cherrypy.config.update({'tools.precontroller.on': 'True'})
		
	# Setup the application related static content directories
	conf = {'/':   {'tools.staticdir.root': os.path.join(current_dir \
				, 'applications', appname, 'static')} \
			,'/media': { 'tools.precontroller.on': False, \
					'tools.staticdir.on': True, \
					'tools.staticdir.dir': os.path.join(current_dir \
				, 'media')}				
		   ,'/images': {'tools.precontroller.on': False, \
					'tools.staticdir.on': True, \
					'tools.staticdir.dir': os.path.join(current_dir \
				, 'applications', appname, 'static','images')}
			,'/css': {'tools.precontroller.on': False, \
					'tools.staticdir.on': True, \
					'tools.staticdir.dir': os.path.join(current_dir \
				, 'applications', appname, 'static','css')} \
			,'/js': {'tools.precontroller.on': False, \
					'tools.staticdir.on': True, \
					'tools.staticdir.dir': os.path.join(current_dir \
				, 'applications', appname, 'static', 'js')} \
				## Common static \
			,'/common/css': {'tools.precontroller.on': False, \
					'tools.staticdir.on': True, \
					'tools.staticdir.dir': os.path.join(current_dir \
				, 'common', 'static','css')}
			,'/common/js': {'tools.precontroller.on': False, \
					'tools.staticdir.on': True, \
					'tools.staticdir.dir': os.path.join(current_dir \
				, "common", 'static', 'js')}				
				}
				
	return conf

""" Add an username to the admin group """
def add_admin(username):
		from models.user import User, UsersGroup
		admin_group = UsersGroup.query.filter_by(name = "Admin").first()
		if not admin_group: # if not found create it
			admin_group = UsersGroup(name = "Admin")
		user = User.query.filter_by(username = username).first()
		if not user:
			print "User %s is not registed" % username
			return 2
		if admin_group in user.groups:
			print "User %s is already on the admin group." \
				% username
			return 1
		else: # add it
			user.groups.append(admin_group)		
			user.auth = 1
			session.commit()
			print "User %s added to the admin group." \
				% username
		return 0

""" 
	Returns an option parser object with the available 
	command line parameters
"""	
def command_line_parser():
	parser = OptionParser()
	parser.add_option("-a", "--add-admin",
		action = "store", type="string", dest="add_admin",
		help = "Add an user to the admin group" \
		)        				
	parser.add_option("-b", "--bindip", \
		action = "store", type="string", dest="host", \
		help = "set bind address for the listener (default=127.0.0.1)" \
		, default="127.0.0.1")
	parser.add_option("-d", "--database",
		action = "store", type="string", dest="database",
		help = "specificy the database URI\n\n" \
		"Examples\n\n" \
		"   mysql://user:password@localhost/database" \
		"   sqlite:///database" \
		)        
	parser.add_option("-e", "--daemon", \
		action = "store_true", dest="daemon", default=False, \
		help = "run process as a daemon (background)")		
	parser.add_option("-f", "--force-view", \
		action = "store", type="string", dest="force_view", \
		help = "force a specific view to be server for all requests\n" \
			"Usefull if you need to provide a maintenance warning")
	parser.add_option("-l", "--console-log", \
		action = "store_true", dest="console_log", default=False, \
		help = "print the http log to the console")
	parser.add_option("-p", "--port", \
		action = "store", type="string", dest="port", \
		help = "set bind address for the listener (default=8080)" \
			, default="8080" )                  
	parser.add_option("-s", "--sql-echo", \
		action = "store_true", dest="sql_echo", default=False, \
		help = "echo the sql statements")
	return parser


""" Main code """
if __name__ == '__main__':    
	current_dir = os.path.dirname(os.path.abspath(__file__))

	# We need this for common models import
	commons_dir = os.path.join(current_dir, 'common')
	if not commons_dir in sys.path:
		sys.path.insert(0, commons_dir)            
	import cherrypy_mako
    
    # Get the command line options
	(options, args) = command_line_parser().parse_args()
	if len(args) < 1:
		print "Usage: %s [options]" % os.path.basename(__file__)
		sys.exit(2)
	appname = args[0]
    
	# Check if there is an application on the expected directory
	if not os.path.isdir(os.path.join(current_dir, "applications" \
		, appname)):
		print "No application %s on the applications directory" \
			% appname
		sys.exit(3)
		
	# We set the database engine here
	db_url = options.database or ("sqlite:///%s.db" % appname)
	# stdin is a special url which prompts for the database url
	# this allows to start the process without having the url included
	# on the system command startup arguments
	if db_url == 'stdin': # special case to request from stdin (secure)
		db_url = raw_input('Please enter the db url:')
		print
	metadata.bind = db_url
	metadata.bind.echo = options.sql_echo

    # Handle --add-admin
	if options.add_admin:
		rc = add_admin(options.add_admin)
		sys.exit(rc)
		
	# Set the app server configuration				
	conf = set_cherrypy_config(appname, db_url, metadata.bind.echo)	
	
	# Set rotating logs
	set_rotating_logs(cherrypy, appname)	
	
	# Set directories for the templating engine
	app_dir = os.path.join(current_dir, 'applications', appname)
	cherrypy_mako.set_directories(
		[os.path.join(app_dir,'views') \
			, os.path.join(current_dir, 'common', 'views') \
		] \
		, os.path.join(tempfile.mkdtemp())
		)
	
	
	# Set the web root handler
	# If the force view parameter is used then we need to use a special
	# web root controlller which handles web_root/* unlike the regular
	if options.force_view:
		print "Forcing the template to", options.force_View
		cherrypy.root = RootForce(options.force_view)
	else:
		cherrypy.root = Root()                    
		app_startup_module = os.path.join(app_dir, 'startup.py')
		if os.path.exists(app_startup_module):		
			sys.path.insert(0, app_dir)
			import startup
			sys.path.remove(app_dir)
		
		    
	# Start the server
	cherrypy.quickstart(cherrypy.root, '/', config=conf)