comparison foodtruck/sites.py @ 587:d4a01a023998

admin: Add "FoodTruck" admin panel from the side experiment project.
author Ludovic Chabant <ludovic@chabant.com>
date Sat, 16 Jan 2016 14:24:35 -0800
parents
children e2c91ba44d6c
comparison
equal deleted inserted replaced
586:59268b4d8c71 587:d4a01a023998
1 import os
2 import os.path
3 import shlex
4 import logging
5 import threading
6 import subprocess
7 from piecrust.app import PieCrust
8
9
10 logger = logging.getLogger(__name__)
11
12
13 class UnauthorizedSiteAccessError(Exception):
14 pass
15
16
17 class Site(object):
18 def __init__(self, name, root_dir, config):
19 self.name = name
20 self.root_dir = root_dir
21 self.config = config
22 self._piecrust_app = None
23 self._scm = None
24 self._bake_thread = None
25 logger.debug("Creating site object for %s" % self.name)
26
27 @property
28 def piecrust_app(self):
29 if self._piecrust_app is None:
30 s = PieCrust(self.root_dir)
31 s.config.set('site/root', '/site/%s/' % self.name)
32 self._piecrust_app = s
33 return self._piecrust_app
34
35 @property
36 def scm(self):
37 if self._scm is None:
38 scm_type = self.config.get('scm/type')
39 if scm_type == 'hg':
40 from .scm.mercurial import MercurialSourceControl
41 self._scm = MercurialSourceControl(self.root_dir)
42 else:
43 raise NotImplementedError()
44 return self._scm
45
46 @property
47 def is_bake_running(self):
48 return self._bake_thread is not None and self._bake_thread.is_alive()
49
50 @property
51 def bake_thread(self):
52 return self._bake_thread
53
54 def bake(self):
55 bake_cmd = self.config.get('triggers/bake')
56 bake_args = shlex.split(bake_cmd)
57
58 logger.debug("Running bake: %s" % bake_args)
59 proc = subprocess.Popen(bake_args, cwd=self.root_dir,
60 stdout=subprocess.PIPE,
61 stderr=subprocess.PIPE)
62
63 pid_file_path = os.path.join(self.root_dir, 'foodtruck_bake.pid')
64 with open(pid_file_path, 'w') as fp:
65 fp.write(str(proc.pid))
66
67 logger.debug("Running bake monitor for PID %d" % proc.pid)
68 self._bake_thread = _BakeThread(self.name, self.root_dir, proc,
69 self._onBakeEnd)
70 self._bake_thread.start()
71
72 def _onBakeEnd(self):
73 os.unlink(os.path.join(self.root_dir, 'foodtruck_bake.pid'))
74 self._bake_thread = None
75
76
77 class _BakeThread(threading.Thread):
78 def __init__(self, sitename, siteroot, proc, callback):
79 super(_BakeThread, self).__init__(
80 name='%s_bake' % sitename, daemon=True)
81 self.sitename = sitename
82 self.siteroot = siteroot
83 self.proc = proc
84 self.callback = callback
85
86 log_file_path = os.path.join(self.siteroot, 'foodtruck_bake.log')
87 self.log_fp = open(log_file_path, 'w', encoding='utf8')
88
89 def run(self):
90 for line in self.proc.stdout:
91 self.log_fp.write(line.decode('utf8'))
92 for line in self.proc.stderr:
93 self.log_fp.write(line.decode('utf8'))
94 self.proc.communicate()
95 if self.proc.returncode != 0:
96 self.log_fp.write("Error, bake process returned code %d" %
97 self.proc.returncode)
98 self.log_fp.close()
99
100 logger.debug("Bake ended for %s." % self.sitename)
101 self.callback()
102
103
104 class FoodTruckSites():
105 def __init__(self, config, current_site=None):
106 self._sites = {}
107 self._site_dirs = {}
108 self.config = config
109 self.current_site = current_site
110
111 def get_root_dir(self, name=None):
112 name = name or self.current_site
113 s = self._site_dirs.get(name)
114 if s:
115 return s
116
117 scfg = self.config.get('sites/%s' % name)
118 root_dir = scfg.get('path')
119 if root_dir is None:
120 raise Exception("Site '%s' has no path defined." % name)
121 if not os.path.isdir(root_dir):
122 raise Exception("Site '%s' has an invalid path." % name)
123 self._site_dirs[name] = root_dir
124 return root_dir
125
126 def get(self, name=None):
127 name = name or self.current_site
128 s = self._sites.get(name)
129 if s:
130 return s
131
132 root_dir = self.get_root_dir(name)
133 s = Site(name, root_dir, self.config)
134 self._sites[name] = s
135 return s
136