##// END OF EJS Templates
Made repos path config configurable from pylons app configs. update Readme
marcink -
r241:48727add default
parent child Browse files
Show More
@@ -1,18 +1,24 b''
1 Pylons based replacement for hgwebdir. Fully customizable,
1 Pylons based replacement for hgwebdir. Fully customizable,
2 with authentication, permissions. Based on vcs library.
2 with authentication, permissions. Based on vcs library.
3 - has it's own middleware to handle mercurial protocol request each request can
3 - has it's own middleware to handle mercurial protocol request each request can
4 be logged and authenticated +threaded performance unlikely to hgweb
4 be logged and authenticated +threaded performance unlikely to hgweb
5 - mako templates let's you cusmotize look and feel of appplication.
5 - mako templates let's you cusmotize look and feel of appplication.
6 - diffs annotations and source code all colored by pygments.
6 - diffs annotations and source code all colored by pygments.
7 - admin interface for performing user/permission managments as well as repository
7 - admin interface for performing user/permission managments as well as repository
8 managment
8 managment
9 - added cache with invalidation on push/repo managment for high performance and
9 - added cache with invalidation on push/repo managment for high performance and
10 always upto date data.
10 always upto date data.
11 - rss /atom feed customizable
11 - rss /atom feed customizable
12 - future support for git
12 - future support for git
13 - based on pylons 1.0 / sqlalchemy 0.6
13 - based on pylons 1.0 / sqlalchemy 0.6
14
14
15 ===
15 ===
16 This software is still in beta mode. I don't guarantee that it'll work.
16 This software is still in beta mode. I don't guarantee that it'll work.
17 I started this project since i was tired of sad looks, and zero controll over
17 I started this project since i was tired of sad looks, and zero controll over
18 our company regular hgwebdir.
18 our company regular hgwebdir.
19
20
21 == INSTALATION
22 run dbmanage.py from pylons_app/lib it should create all needed table and
23 an admin account, Edit file repositories.config and change the path for you
24 mercurial repositories, remember about permissions. No newline at end of file
@@ -1,135 +1,136 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # pylons_app - Pylons environment configuration #
3 # pylons_app - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 ############################################
10 ############################################
11 ## Uncomment and replace with the address ##
11 ## Uncomment and replace with the address ##
12 ## which should receive any error reports ##
12 ## which should receive any error reports ##
13 ############################################
13 ############################################
14 #email_to = marcin.kuzminski@etelko.pl
14 #email_to = marcin.kuzminski@etelko.pl
15 #smtp_server = mail.etelko.pl
15 #smtp_server = mail.etelko.pl
16 #error_email_from = paste_error@localhost
16 #error_email_from = paste_error@localhost
17 #smtp_username =
17 #smtp_username =
18 #smtp_password =
18 #smtp_password =
19 #error_message = 'mercurial crash !'
19 #error_message = 'mercurial crash !'
20
20
21 [server:main]
21 [server:main]
22 ##nr of threads to spawn
22 ##nr of threads to spawn
23 threadpool_workers = 5
23 threadpool_workers = 5
24
24
25 ##max request before
25 ##max request before
26 threadpool_max_requests = 2
26 threadpool_max_requests = 2
27
27
28 ##option to use threads of process
28 ##option to use threads of process
29 use_threadpool = true
29 use_threadpool = true
30
30
31 use = egg:Paste#http
31 use = egg:Paste#http
32 host = 127.0.0.1
32 host = 127.0.0.1
33 port = 5000
33 port = 5000
34
34
35 [app:main]
35 [app:main]
36 use = egg:pylons_app
36 use = egg:pylons_app
37 full_stack = true
37 full_stack = true
38 static_files = false
38 static_files = false
39 lang=en
39 lang=en
40 cache_dir = %(here)s/data
40 cache_dir = %(here)s/data
41 ##a name for our application
41 ##a name for our application
42 hg_app_name = Python-works
42 hg_app_name = Python-works
43 hg_app_repo_conf = repositories.config
43
44
44 ####################################
45 ####################################
45 ### BEAKER CACHE ####
46 ### BEAKER CACHE ####
46 ####################################
47 ####################################
47 beaker.cache.data_dir=/tmp/cache/data
48 beaker.cache.data_dir=/tmp/cache/data
48 beaker.cache.lock_dir=/tmp/cache/lock
49 beaker.cache.lock_dir=/tmp/cache/lock
49 beaker.cache.regions=short_term,long_term
50 beaker.cache.regions=short_term,long_term
50 beaker.cache.long_term.type=memory
51 beaker.cache.long_term.type=memory
51 beaker.cache.long_term.expire=36000
52 beaker.cache.long_term.expire=36000
52 beaker.cache.short_term.type=memory
53 beaker.cache.short_term.type=memory
53 beaker.cache.short_term.expire=60
54 beaker.cache.short_term.expire=60
54
55
55 ################################################################################
56 ################################################################################
56 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
57 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
57 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
58 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
58 ## execute malicious code after an exception is raised. ##
59 ## execute malicious code after an exception is raised. ##
59 ################################################################################
60 ################################################################################
60 #set debug = false
61 #set debug = false
61
62
62 ##################################
63 ##################################
63 ### LOGVIEW CONFIG ###
64 ### LOGVIEW CONFIG ###
64 ##################################
65 ##################################
65 logview.sqlalchemy = #faa
66 logview.sqlalchemy = #faa
66 logview.pylons.templating = #bfb
67 logview.pylons.templating = #bfb
67 logview.pylons.util = #eee
68 logview.pylons.util = #eee
68
69
69 #########################################################
70 #########################################################
70 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
71 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
71 #########################################################
72 #########################################################
72 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
73 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
73 #sqlalchemy.db1.echo = False
74 #sqlalchemy.db1.echo = False
74 #sqlalchemy.db1.pool_recycle = 3600
75 #sqlalchemy.db1.pool_recycle = 3600
75 sqlalchemy.convert_unicode = true
76 sqlalchemy.convert_unicode = true
76
77
77 ################################
78 ################################
78 ### LOGGING CONFIGURATION ####
79 ### LOGGING CONFIGURATION ####
79 ################################
80 ################################
80 [loggers]
81 [loggers]
81 keys = root, routes, pylons_app, sqlalchemy
82 keys = root, routes, pylons_app, sqlalchemy
82
83
83 [handlers]
84 [handlers]
84 keys = console
85 keys = console
85
86
86 [formatters]
87 [formatters]
87 keys = generic,color_formatter
88 keys = generic,color_formatter
88
89
89 #############
90 #############
90 ## LOGGERS ##
91 ## LOGGERS ##
91 #############
92 #############
92 [logger_root]
93 [logger_root]
93 level = NOTSET
94 level = NOTSET
94 handlers = console
95 handlers = console
95
96
96 [logger_routes]
97 [logger_routes]
97 level = DEBUG
98 level = DEBUG
98 handlers = console
99 handlers = console
99 qualname = routes.middleware
100 qualname = routes.middleware
100 # "level = DEBUG" logs the route matched and routing variables.
101 # "level = DEBUG" logs the route matched and routing variables.
101
102
102 [logger_pylons_app]
103 [logger_pylons_app]
103 level = DEBUG
104 level = DEBUG
104 handlers = console
105 handlers = console
105 qualname = pylons_app
106 qualname = pylons_app
106 propagate = 0
107 propagate = 0
107
108
108 [logger_sqlalchemy]
109 [logger_sqlalchemy]
109 level = ERROR
110 level = ERROR
110 handlers = console
111 handlers = console
111 qualname = sqlalchemy.engine
112 qualname = sqlalchemy.engine
112 propagate = 0
113 propagate = 0
113
114
114 ##############
115 ##############
115 ## HANDLERS ##
116 ## HANDLERS ##
116 ##############
117 ##############
117
118
118 [handler_console]
119 [handler_console]
119 class = StreamHandler
120 class = StreamHandler
120 args = (sys.stderr,)
121 args = (sys.stderr,)
121 level = NOTSET
122 level = NOTSET
122 formatter = color_formatter
123 formatter = color_formatter
123
124
124 ################
125 ################
125 ## FORMATTERS ##
126 ## FORMATTERS ##
126 ################
127 ################
127
128
128 [formatter_generic]
129 [formatter_generic]
129 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
130 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
130 datefmt = %Y-%m-%d %H:%M:%S
131 datefmt = %Y-%m-%d %H:%M:%S
131
132
132 [formatter_color_formatter]
133 [formatter_color_formatter]
133 class=pylons_app.lib.colored_formatter.ColorFormatter
134 class=pylons_app.lib.colored_formatter.ColorFormatter
134 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
135 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
135 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
136 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,135 +1,136 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # pylons_app - Pylons environment configuration #
3 # pylons_app - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 ############################################
10 ############################################
11 ## Uncomment and replace with the address ##
11 ## Uncomment and replace with the address ##
12 ## which should receive any error reports ##
12 ## which should receive any error reports ##
13 ############################################
13 ############################################
14 #email_to = marcin.kuzminski@etelko.pl
14 #email_to = marcin.kuzminski@etelko.pl
15 #smtp_server = mail.etelko.pl
15 #smtp_server = mail.etelko.pl
16 #error_email_from = paste_error@localhost
16 #error_email_from = paste_error@localhost
17 #smtp_username =
17 #smtp_username =
18 #smtp_password =
18 #smtp_password =
19 #error_message = 'mercurial crash !'
19 #error_message = 'mercurial crash !'
20
20
21 [server:main]
21 [server:main]
22 ##nr of threads to spawn
22 ##nr of threads to spawn
23 threadpool_workers = 5
23 threadpool_workers = 5
24
24
25 ##max request before
25 ##max request before
26 threadpool_max_requests = 2
26 threadpool_max_requests = 2
27
27
28 ##option to use threads of process
28 ##option to use threads of process
29 use_threadpool = true
29 use_threadpool = true
30
30
31 use = egg:Paste#http
31 use = egg:Paste#http
32 host = 127.0.0.1
32 host = 127.0.0.1
33 port = 8001
33 port = 8001
34
34
35 [app:main]
35 [app:main]
36 use = egg:pylons_app
36 use = egg:pylons_app
37 full_stack = true
37 full_stack = true
38 static_files = false
38 static_files = false
39 lang=en
39 lang=en
40 cache_dir = %(here)s/data
40 cache_dir = %(here)s/data
41 ##a name for our application
41 ##a name for our application
42 hg_app_name = Python-works
42 hg_app_name = Python-works
43 hg_app_repo_conf = repositories.config
43
44
44 ####################################
45 ####################################
45 ### BEAKER CACHE ####
46 ### BEAKER CACHE ####
46 ####################################
47 ####################################
47 beaker.cache.data_dir=/tmp/cache/data
48 beaker.cache.data_dir=/tmp/cache/data
48 beaker.cache.lock_dir=/tmp/cache/lock
49 beaker.cache.lock_dir=/tmp/cache/lock
49 beaker.cache.regions=short_term,long_term
50 beaker.cache.regions=short_term,long_term
50 beaker.cache.long_term.type=memory
51 beaker.cache.long_term.type=memory
51 beaker.cache.long_term.expire=36000
52 beaker.cache.long_term.expire=36000
52 beaker.cache.short_term.type=memory
53 beaker.cache.short_term.type=memory
53 beaker.cache.short_term.expire=60
54 beaker.cache.short_term.expire=60
54
55
55 ################################################################################
56 ################################################################################
56 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
57 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
57 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
58 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
58 ## execute malicious code after an exception is raised. ##
59 ## execute malicious code after an exception is raised. ##
59 ################################################################################
60 ################################################################################
60 set debug = false
61 set debug = false
61
62
62 ##################################
63 ##################################
63 ### LOGVIEW CONFIG ###
64 ### LOGVIEW CONFIG ###
64 ##################################
65 ##################################
65 logview.sqlalchemy = #faa
66 logview.sqlalchemy = #faa
66 logview.pylons.templating = #bfb
67 logview.pylons.templating = #bfb
67 logview.pylons.util = #eee
68 logview.pylons.util = #eee
68
69
69 #########################################################
70 #########################################################
70 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
71 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
71 #########################################################
72 #########################################################
72 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
73 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
73 #sqlalchemy.db1.echo = False
74 #sqlalchemy.db1.echo = False
74 #sqlalchemy.db1.pool_recycle = 3600
75 #sqlalchemy.db1.pool_recycle = 3600
75 sqlalchemy.convert_unicode = true
76 sqlalchemy.convert_unicode = true
76
77
77 ################################
78 ################################
78 ### LOGGING CONFIGURATION ####
79 ### LOGGING CONFIGURATION ####
79 ################################
80 ################################
80 [loggers]
81 [loggers]
81 keys = root, routes, pylons_app, sqlalchemy
82 keys = root, routes, pylons_app, sqlalchemy
82
83
83 [handlers]
84 [handlers]
84 keys = console
85 keys = console
85
86
86 [formatters]
87 [formatters]
87 keys = generic,color_formatter
88 keys = generic,color_formatter
88
89
89 #############
90 #############
90 ## LOGGERS ##
91 ## LOGGERS ##
91 #############
92 #############
92 [logger_root]
93 [logger_root]
93 level = INFO
94 level = INFO
94 handlers = console
95 handlers = console
95
96
96 [logger_routes]
97 [logger_routes]
97 level = INFO
98 level = INFO
98 handlers = console
99 handlers = console
99 qualname = routes.middleware
100 qualname = routes.middleware
100 # "level = DEBUG" logs the route matched and routing variables.
101 # "level = DEBUG" logs the route matched and routing variables.
101
102
102 [logger_pylons_app]
103 [logger_pylons_app]
103 level = DEBUG
104 level = DEBUG
104 handlers = console
105 handlers = console
105 qualname = pylons_app
106 qualname = pylons_app
106 propagate = 0
107 propagate = 0
107
108
108 [logger_sqlalchemy]
109 [logger_sqlalchemy]
109 level = ERROR
110 level = ERROR
110 handlers = console
111 handlers = console
111 qualname = sqlalchemy.engine
112 qualname = sqlalchemy.engine
112 propagate = 0
113 propagate = 0
113
114
114 ##############
115 ##############
115 ## HANDLERS ##
116 ## HANDLERS ##
116 ##############
117 ##############
117
118
118 [handler_console]
119 [handler_console]
119 class = StreamHandler
120 class = StreamHandler
120 args = (sys.stderr,)
121 args = (sys.stderr,)
121 level = NOTSET
122 level = NOTSET
122 formatter = color_formatter
123 formatter = color_formatter
123
124
124 ################
125 ################
125 ## FORMATTERS ##
126 ## FORMATTERS ##
126 ################
127 ################
127
128
128 [formatter_generic]
129 [formatter_generic]
129 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
130 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
130 datefmt = %Y-%m-%d %H:%M:%S
131 datefmt = %Y-%m-%d %H:%M:%S
131
132
132 [formatter_color_formatter]
133 [formatter_color_formatter]
133 class=pylons_app.lib.colored_formatter.ColorFormatter
134 class=pylons_app.lib.colored_formatter.ColorFormatter
134 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
135 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
135 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
136 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,25 +1,25 b''
1 """The application's Globals object"""
1 """The application's Globals object"""
2
2
3 from beaker.cache import CacheManager
3 from beaker.cache import CacheManager
4 from beaker.util import parse_cache_config_options
4 from beaker.util import parse_cache_config_options
5 from pylons_app.lib.utils import make_ui
5 from pylons_app.lib.utils import make_ui
6
6
7 class Globals(object):
7 class Globals(object):
8
8
9 """Globals acts as a container for objects available throughout the
9 """Globals acts as a container for objects available throughout the
10 life of the application
10 life of the application
11
11
12 """
12 """
13
13
14 def __init__(self, config):
14 def __init__(self, config):
15 """One instance of Globals is created during application
15 """One instance of Globals is created during application
16 initialization and is available during requests via the
16 initialization and is available during requests via the
17 'app_globals' variable
17 'app_globals' variable
18
18
19 """
19 """
20 self.cache = CacheManager(**parse_cache_config_options(config))
20 self.cache = CacheManager(**parse_cache_config_options(config))
21 self.baseui = make_ui('hgwebdir.config')
21 self.baseui = make_ui(config['hg_app_repo_conf'])
22 self.paths = self.baseui.configitems('paths')
22 self.paths = self.baseui.configitems('paths')
23 self.base_path = self.paths[0][1].replace('*', '')
23 self.base_path = self.paths[0][1].replace('*', '')
24 self.changeset_annotation_colors = {}
24 self.changeset_annotation_colors = {}
25 self.available_permissions = None # propagated after init_model
25 self.available_permissions = None # propagated after init_model
@@ -1,88 +1,88 b''
1 '''BACKUP MANAGER'''
1 '''BACKUP MANAGER'''
2 import logging
2 import logging
3 from mercurial import config
3 from mercurial import config
4 import tarfile
4 import tarfile
5 import os
5 import os
6 import datetime
6 import datetime
7 import sys
7 import sys
8 import subprocess
8 import subprocess
9 logging.basicConfig(level=logging.DEBUG,
9 logging.basicConfig(level=logging.DEBUG,
10 format="%(asctime)s %(levelname)-5.5s %(message)s")
10 format="%(asctime)s %(levelname)-5.5s %(message)s")
11
11
12 class BackupManager(object):
12 class BackupManager(object):
13 def __init__(self):
13 def __init__(self, id_rsa_path, repo_conf):
14 self.repos_path = None
14 self.repos_path = None
15 self.backup_file_name = None
15 self.backup_file_name = None
16 self.id_rsa_path = '/home/pylons/id_rsa'
16 self.id_rsa_path = id_rsa_path
17 self.check_id_rsa()
17 self.check_id_rsa()
18 cur_dir = os.path.realpath(__file__)
18 cur_dir = os.path.realpath(__file__)
19 dn = os.path.dirname
19 dn = os.path.dirname
20 self.backup_file_path = os.path.join(dn(dn(dn(cur_dir))), 'data')
20 self.backup_file_path = os.path.join(dn(dn(dn(cur_dir))), 'data')
21 cfg = config.config()
21 cfg = config.config()
22 try:
22 try:
23 cfg.read(os.path.join(dn(dn(dn(cur_dir))), 'hgwebdir.config'))
23 cfg.read(os.path.join(dn(dn(dn(cur_dir))), repo_conf))
24 except IOError:
24 except IOError:
25 logging.error('Could not read hgwebdir.config')
25 logging.error('Could not read %s', repo_conf)
26 sys.exit()
26 sys.exit()
27 self.set_repos_path(cfg.items('paths'))
27 self.set_repos_path(cfg.items('paths'))
28 logging.info('starting backup for %s', self.repos_path)
28 logging.info('starting backup for %s', self.repos_path)
29 logging.info('backup target %s', self.backup_file_path)
29 logging.info('backup target %s', self.backup_file_path)
30
30
31 if not os.path.isdir(self.repos_path):
31 if not os.path.isdir(self.repos_path):
32 raise Exception('Not a valid directory in %s' % self.repos_path)
32 raise Exception('Not a valid directory in %s' % self.repos_path)
33
33
34 def check_id_rsa(self):
34 def check_id_rsa(self):
35 if not os.path.isfile(self.id_rsa_path):
35 if not os.path.isfile(self.id_rsa_path):
36 logging.error('Could not load id_rsa key file in %s',
36 logging.error('Could not load id_rsa key file in %s',
37 self.id_rsa_path)
37 self.id_rsa_path)
38 sys.exit()
38 sys.exit()
39
39
40 def set_repos_path(self, paths):
40 def set_repos_path(self, paths):
41 repos_path = paths[0][1].split('/')
41 repos_path = paths[0][1].split('/')
42 if repos_path[-1] in ['*', '**']:
42 if repos_path[-1] in ['*', '**']:
43 repos_path = repos_path[:-1]
43 repos_path = repos_path[:-1]
44 if repos_path[0] != '/':
44 if repos_path[0] != '/':
45 repos_path[0] = '/'
45 repos_path[0] = '/'
46 self.repos_path = os.path.join(*repos_path)
46 self.repos_path = os.path.join(*repos_path)
47
47
48 def backup_repos(self):
48 def backup_repos(self):
49 today = datetime.datetime.now().weekday() + 1
49 today = datetime.datetime.now().weekday() + 1
50 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
50 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
51 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
51 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
52 tar = tarfile.open(bckp_file, "w:gz")
52 tar = tarfile.open(bckp_file, "w:gz")
53
53
54 for dir_name in os.listdir(self.repos_path):
54 for dir_name in os.listdir(self.repos_path):
55 logging.info('backing up %s', dir_name)
55 logging.info('backing up %s', dir_name)
56 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
56 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
57 tar.close()
57 tar.close()
58 logging.info('finished backup of mercurial repositories')
58 logging.info('finished backup of mercurial repositories')
59
59
60
60
61
61
62 def transfer_files(self):
62 def transfer_files(self):
63 params = {
63 params = {
64 'id_rsa_key': self.id_rsa_path,
64 'id_rsa_key': self.id_rsa_path,
65 'backup_file_path':self.backup_file_path,
65 'backup_file_path':self.backup_file_path,
66 'backup_file_name':self.backup_file_name,
66 'backup_file_name':self.backup_file_name,
67 }
67 }
68 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
68 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
69 '%(backup_file_path)s/%(backup_file_name)s' % params,
69 '%(backup_file_path)s/%(backup_file_name)s' % params,
70 'root@192.168.2.102:/backups/mercurial' % params]
70 'root@192.168.2.102:/backups/mercurial' % params]
71
71
72 subprocess.call(cmd)
72 subprocess.call(cmd)
73 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
73 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
74
74
75
75
76 def rm_file(self):
76 def rm_file(self):
77 logging.info('Removing file %s', self.backup_file_name)
77 logging.info('Removing file %s', self.backup_file_name)
78 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
78 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
79
79
80
80
81
81
82 if __name__ == "__main__":
82 if __name__ == "__main__":
83 B_MANAGER = BackupManager()
83 B_MANAGER = BackupManager('/home/pylons/id_rsa', 'repositories.config')
84 B_MANAGER.backup_repos()
84 B_MANAGER.backup_repos()
85 B_MANAGER.transfer_files()
85 B_MANAGER.transfer_files()
86 B_MANAGER.rm_file()
86 B_MANAGER.rm_file()
87
87
88
88
@@ -1,136 +1,136 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 #
3 #
4 # Copyright (c) 2010 marcink. All rights reserved.
4 # Copyright (c) 2010 marcink. All rights reserved.
5 #
5 #
6 """
6 """
7 Created on 2010-04-28
7 Created on 2010-04-28
8
8
9 @author: marcink
9 @author: marcink
10 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
10 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
11 It's implemented with basic auth function
11 It's implemented with basic auth function
12 """
12 """
13 from datetime import datetime
13 from datetime import datetime
14 from mercurial.hgweb import hgweb
14 from mercurial.hgweb import hgweb
15 from mercurial.hgweb.request import wsgiapplication
15 from mercurial.hgweb.request import wsgiapplication
16 from paste.auth.basic import AuthBasicAuthenticator
16 from paste.auth.basic import AuthBasicAuthenticator
17 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
17 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
18 from pylons_app.lib.auth import authfunc
18 from pylons_app.lib.auth import authfunc
19 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache
19 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache
20 from pylons_app.model import meta
20 from pylons_app.model import meta
21 from pylons_app.model.db import UserLog, User
21 from pylons_app.model.db import UserLog, User
22 from webob.exc import HTTPNotFound
22 from webob.exc import HTTPNotFound
23 import logging
23 import logging
24 import os
24 import os
25 log = logging.getLogger(__name__)
25 log = logging.getLogger(__name__)
26
26
27 class SimpleHg(object):
27 class SimpleHg(object):
28
28
29 def __init__(self, application, config):
29 def __init__(self, application, config):
30 self.application = application
30 self.application = application
31 self.config = config
31 self.config = config
32 #authenticate this mercurial request using
32 #authenticate this mercurial request using
33 realm = '%s %s' % (config['hg_app_name'], 'mercurial repository')
33 realm = '%s %s' % (config['hg_app_name'], 'mercurial repository')
34 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
34 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
35
35
36 def __call__(self, environ, start_response):
36 def __call__(self, environ, start_response):
37 if not is_mercurial(environ):
37 if not is_mercurial(environ):
38 return self.application(environ, start_response)
38 return self.application(environ, start_response)
39 else:
39 else:
40 #===================================================================
40 #===================================================================
41 # AUTHENTICATE THIS MERCURIAL REQUEST
41 # AUTHENTICATE THIS MERCURIAL REQUEST
42 #===================================================================
42 #===================================================================
43 username = REMOTE_USER(environ)
43 username = REMOTE_USER(environ)
44 if not username:
44 if not username:
45 result = self.authenticate(environ)
45 result = self.authenticate(environ)
46 if isinstance(result, str):
46 if isinstance(result, str):
47 AUTH_TYPE.update(environ, 'basic')
47 AUTH_TYPE.update(environ, 'basic')
48 REMOTE_USER.update(environ, result)
48 REMOTE_USER.update(environ, result)
49 else:
49 else:
50 return result.wsgi_application(environ, start_response)
50 return result.wsgi_application(environ, start_response)
51
51
52 try:
52 try:
53 repo_name = environ['PATH_INFO'].split('/')[1]
53 repo_name = environ['PATH_INFO'].split('/')[1]
54 except:
54 except:
55 return HTTPNotFound()(environ, start_response)
55 return HTTPNotFound()(environ, start_response)
56
56
57 #since we wrap into hgweb, just reset the path
57 #since we wrap into hgweb, just reset the path
58 environ['PATH_INFO'] = '/'
58 environ['PATH_INFO'] = '/'
59 self.baseui = make_ui()
59 self.baseui = make_ui(self.config['hg_app_repo_conf'])
60 self.basepath = self.baseui.configitems('paths')[0][1]\
60 self.basepath = self.baseui.configitems('paths')[0][1]\
61 .replace('*', '')
61 .replace('*', '')
62 self.repo_path = os.path.join(self.basepath, repo_name)
62 self.repo_path = os.path.join(self.basepath, repo_name)
63 try:
63 try:
64 app = wsgiapplication(self.__make_app)
64 app = wsgiapplication(self.__make_app)
65 except Exception as e:
65 except Exception as e:
66 return HTTPNotFound()(environ, start_response)
66 return HTTPNotFound()(environ, start_response)
67 action = self.__get_action(environ)
67 action = self.__get_action(environ)
68 #invalidate cache on push
68 #invalidate cache on push
69 if action == 'push':
69 if action == 'push':
70 self.__invalidate_cache(repo_name)
70 self.__invalidate_cache(repo_name)
71
71
72 if action:
72 if action:
73 username = self.__get_environ_user(environ)
73 username = self.__get_environ_user(environ)
74 self.__log_user_action(username, action, repo_name)
74 self.__log_user_action(username, action, repo_name)
75
75
76 return app(environ, start_response)
76 return app(environ, start_response)
77
77
78 def __make_app(self):
78 def __make_app(self):
79 hgserve = hgweb(self.repo_path)
79 hgserve = hgweb(self.repo_path)
80 return self.__load_web_settings(hgserve)
80 return self.__load_web_settings(hgserve)
81
81
82 def __get_environ_user(self, environ):
82 def __get_environ_user(self, environ):
83 return environ.get('REMOTE_USER')
83 return environ.get('REMOTE_USER')
84
84
85 def __get_action(self, environ):
85 def __get_action(self, environ):
86 """
86 """
87 Maps mercurial request commands into a pull or push command.
87 Maps mercurial request commands into a pull or push command.
88 @param environ:
88 @param environ:
89 """
89 """
90 mapping = {
90 mapping = {
91 'changegroup': 'pull',
91 'changegroup': 'pull',
92 'changegroupsubset': 'pull',
92 'changegroupsubset': 'pull',
93 'unbundle': 'push',
93 'unbundle': 'push',
94 'stream_out': 'pull',
94 'stream_out': 'pull',
95 }
95 }
96 for qry in environ['QUERY_STRING'].split('&'):
96 for qry in environ['QUERY_STRING'].split('&'):
97 if qry.startswith('cmd'):
97 if qry.startswith('cmd'):
98 cmd = qry.split('=')[-1]
98 cmd = qry.split('=')[-1]
99 if mapping.has_key(cmd):
99 if mapping.has_key(cmd):
100 return mapping[cmd]
100 return mapping[cmd]
101
101
102 def __log_user_action(self, username, action, repo):
102 def __log_user_action(self, username, action, repo):
103 sa = meta.Session
103 sa = meta.Session
104 try:
104 try:
105 user = sa.query(User).filter(User.username == username).one()
105 user = sa.query(User).filter(User.username == username).one()
106 user_log = UserLog()
106 user_log = UserLog()
107 user_log.user_id = user.user_id
107 user_log.user_id = user.user_id
108 user_log.action = action
108 user_log.action = action
109 user_log.repository = repo.replace('/', '')
109 user_log.repository = repo.replace('/', '')
110 user_log.action_date = datetime.now()
110 user_log.action_date = datetime.now()
111 sa.add(user_log)
111 sa.add(user_log)
112 sa.commit()
112 sa.commit()
113 log.info('Adding user %s, action %s on %s',
113 log.info('Adding user %s, action %s on %s',
114 username, action, repo)
114 username, action, repo)
115 except Exception as e:
115 except Exception as e:
116 sa.rollback()
116 sa.rollback()
117 log.error('could not log user action:%s', str(e))
117 log.error('could not log user action:%s', str(e))
118
118
119 def __invalidate_cache(self, repo_name):
119 def __invalidate_cache(self, repo_name):
120 """we know that some change was made to repositories and we should
120 """we know that some change was made to repositories and we should
121 invalidate the cache to see the changes right away but only for
121 invalidate the cache to see the changes right away but only for
122 push requests"""
122 push requests"""
123 invalidate_cache('cached_repo_list')
123 invalidate_cache('cached_repo_list')
124 invalidate_cache('full_changelog', repo_name)
124 invalidate_cache('full_changelog', repo_name)
125
125
126
126
127 def __load_web_settings(self, hgserve):
127 def __load_web_settings(self, hgserve):
128 repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False)
128 repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False)
129 #set the global ui for hgserve
129 #set the global ui for hgserve
130 hgserve.repo.ui = self.baseui
130 hgserve.repo.ui = self.baseui
131
131
132 if repoui:
132 if repoui:
133 #set the repository based config
133 #set the repository based config
134 hgserve.repo.ui = repoui
134 hgserve.repo.ui = repoui
135
135
136 return hgserve
136 return hgserve
@@ -1,127 +1,130 b''
1 import os
1 import os
2 import logging
2 import logging
3 from mercurial import ui, config, hg
3 from mercurial import ui, config, hg
4 from mercurial.error import RepoError
4 from mercurial.error import RepoError
5 log = logging.getLogger(__name__)
5 log = logging.getLogger(__name__)
6
6
7
7
8 def get_repo_slug(request):
8 def get_repo_slug(request):
9 path_info = request.environ.get('PATH_INFO')
9 path_info = request.environ.get('PATH_INFO')
10 uri_lst = path_info.split('/')
10 uri_lst = path_info.split('/')
11 repo_name = uri_lst[1]
11 repo_name = uri_lst[1]
12 return repo_name
12 return repo_name
13
13
14 def is_mercurial(environ):
14 def is_mercurial(environ):
15 """
15 """
16 Returns True if request's target is mercurial server - header
16 Returns True if request's target is mercurial server - header
17 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
17 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
18 """
18 """
19 http_accept = environ.get('HTTP_ACCEPT')
19 http_accept = environ.get('HTTP_ACCEPT')
20 if http_accept and http_accept.startswith('application/mercurial'):
20 if http_accept and http_accept.startswith('application/mercurial'):
21 return True
21 return True
22 return False
22 return False
23
23
24 def check_repo_dir(paths):
24 def check_repo_dir(paths):
25 repos_path = paths[0][1].split('/')
25 repos_path = paths[0][1].split('/')
26 if repos_path[-1] in ['*', '**']:
26 if repos_path[-1] in ['*', '**']:
27 repos_path = repos_path[:-1]
27 repos_path = repos_path[:-1]
28 if repos_path[0] != '/':
28 if repos_path[0] != '/':
29 repos_path[0] = '/'
29 repos_path[0] = '/'
30 if not os.path.isdir(os.path.join(*repos_path)):
30 if not os.path.isdir(os.path.join(*repos_path)):
31 raise Exception('Not a valid repository in %s' % paths[0][1])
31 raise Exception('Not a valid repository in %s' % paths[0][1])
32
32
33 def check_repo(repo_name, base_path):
33 def check_repo(repo_name, base_path):
34
34
35 repo_path = os.path.join(base_path, repo_name)
35 repo_path = os.path.join(base_path, repo_name)
36
36
37 try:
37 try:
38 r = hg.repository(ui.ui(), repo_path)
38 r = hg.repository(ui.ui(), repo_path)
39 hg.verify(r)
39 hg.verify(r)
40 #here we hnow that repo exists it was verified
40 #here we hnow that repo exists it was verified
41 log.info('%s repo is already created', repo_name)
41 log.info('%s repo is already created', repo_name)
42 return False
42 return False
43 #raise Exception('Repo exists')
43 #raise Exception('Repo exists')
44 except RepoError:
44 except RepoError:
45 log.info('%s repo is free for creation', repo_name)
45 log.info('%s repo is free for creation', repo_name)
46 #it means that there is no valid repo there...
46 #it means that there is no valid repo there...
47 return True
47 return True
48
48
49 def make_ui(path='hgwebdir.config', checkpaths=True):
49 def make_ui(path=None, checkpaths=True):
50 """
50 """
51 A funcion that will read python rc files and make an ui from read options
51 A funcion that will read python rc files and make an ui from read options
52
52
53 @param path: path to mercurial config file
53 @param path: path to mercurial config file
54 """
54 """
55 if not path:
56 log.error('repos config path is empty !')
57
55 if not os.path.isfile(path):
58 if not os.path.isfile(path):
56 log.warning('Unable to read config file %s' % path)
59 log.warning('Unable to read config file %s' % path)
57 return False
60 return False
58 #propagated from mercurial documentation
61 #propagated from mercurial documentation
59 sections = [
62 sections = [
60 'alias',
63 'alias',
61 'auth',
64 'auth',
62 'decode/encode',
65 'decode/encode',
63 'defaults',
66 'defaults',
64 'diff',
67 'diff',
65 'email',
68 'email',
66 'extensions',
69 'extensions',
67 'format',
70 'format',
68 'merge-patterns',
71 'merge-patterns',
69 'merge-tools',
72 'merge-tools',
70 'hooks',
73 'hooks',
71 'http_proxy',
74 'http_proxy',
72 'smtp',
75 'smtp',
73 'patch',
76 'patch',
74 'paths',
77 'paths',
75 'profiling',
78 'profiling',
76 'server',
79 'server',
77 'trusted',
80 'trusted',
78 'ui',
81 'ui',
79 'web',
82 'web',
80 ]
83 ]
81
84
82 baseui = ui.ui()
85 baseui = ui.ui()
83 cfg = config.config()
86 cfg = config.config()
84 cfg.read(path)
87 cfg.read(path)
85 if checkpaths:check_repo_dir(cfg.items('paths'))
88 if checkpaths:check_repo_dir(cfg.items('paths'))
86
89
87 for section in sections:
90 for section in sections:
88 for k, v in cfg.items(section):
91 for k, v in cfg.items(section):
89 baseui.setconfig(section, k, v)
92 baseui.setconfig(section, k, v)
90
93
91 return baseui
94 return baseui
92
95
93 def invalidate_cache(name, *args):
96 def invalidate_cache(name, *args):
94 """Invalidates given name cache"""
97 """Invalidates given name cache"""
95
98
96 from beaker.cache import region_invalidate
99 from beaker.cache import region_invalidate
97 log.info('INVALIDATING CACHE FOR %s', name)
100 log.info('INVALIDATING CACHE FOR %s', name)
98
101
99 """propagate our arguments to make sure invalidation works. First
102 """propagate our arguments to make sure invalidation works. First
100 argument has to be the name of cached func name give to cache decorator
103 argument has to be the name of cached func name give to cache decorator
101 without that the invalidation would not work"""
104 without that the invalidation would not work"""
102 tmp = [name]
105 tmp = [name]
103 tmp.extend(args)
106 tmp.extend(args)
104 args = tuple(tmp)
107 args = tuple(tmp)
105
108
106 if name == 'cached_repo_list':
109 if name == 'cached_repo_list':
107 from pylons_app.lib.base import _get_repos_cached
110 from pylons_app.lib.base import _get_repos_cached
108 region_invalidate(_get_repos_cached, None, *args)
111 region_invalidate(_get_repos_cached, None, *args)
109
112
110 if name == 'full_changelog':
113 if name == 'full_changelog':
111 from pylons_app.lib.base import _full_changelog_cached
114 from pylons_app.lib.base import _full_changelog_cached
112 region_invalidate(_full_changelog_cached, None, *args)
115 region_invalidate(_full_changelog_cached, None, *args)
113
116
114 from vcs.backends.base import BaseChangeset
117 from vcs.backends.base import BaseChangeset
115 from vcs.utils.lazy import LazyProperty
118 from vcs.utils.lazy import LazyProperty
116 class EmptyChangeset(BaseChangeset):
119 class EmptyChangeset(BaseChangeset):
117
120
118 revision = -1
121 revision = -1
119
122
120 @LazyProperty
123 @LazyProperty
121 def raw_id(self):
124 def raw_id(self):
122 """
125 """
123 Returns raw string identifing this changeset, useful for web
126 Returns raw string identifing this changeset, useful for web
124 representation.
127 representation.
125 """
128 """
126 return '0' * 12
129 return '0' * 12
127
130
1 NO CONTENT: file renamed from hgwebdir.config to repositories.config
NO CONTENT: file renamed from hgwebdir.config to repositories.config
General Comments 0
You need to be logged in to leave comments. Login now