diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foodtruck/sites.py	Sat Jan 16 14:24:35 2016 -0800
@@ -0,0 +1,136 @@
+import os
+import os.path
+import shlex
+import logging
+import threading
+import subprocess
+from piecrust.app import PieCrust
+
+
+logger = logging.getLogger(__name__)
+
+
+class UnauthorizedSiteAccessError(Exception):
+    pass
+
+
+class Site(object):
+    def __init__(self, name, root_dir, config):
+        self.name = name
+        self.root_dir = root_dir
+        self.config = config
+        self._piecrust_app = None
+        self._scm = None
+        self._bake_thread = None
+        logger.debug("Creating site object for %s" % self.name)
+
+    @property
+    def piecrust_app(self):
+        if self._piecrust_app is None:
+            s = PieCrust(self.root_dir)
+            s.config.set('site/root', '/site/%s/' % self.name)
+            self._piecrust_app = s
+        return self._piecrust_app
+
+    @property
+    def scm(self):
+        if self._scm is None:
+            scm_type = self.config.get('scm/type')
+            if scm_type == 'hg':
+                from .scm.mercurial import MercurialSourceControl
+                self._scm = MercurialSourceControl(self.root_dir)
+            else:
+                raise NotImplementedError()
+        return self._scm
+
+    @property
+    def is_bake_running(self):
+        return self._bake_thread is not None and self._bake_thread.is_alive()
+
+    @property
+    def bake_thread(self):
+        return self._bake_thread
+
+    def bake(self):
+        bake_cmd = self.config.get('triggers/bake')
+        bake_args = shlex.split(bake_cmd)
+
+        logger.debug("Running bake: %s" % bake_args)
+        proc = subprocess.Popen(bake_args, cwd=self.root_dir,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+
+        pid_file_path = os.path.join(self.root_dir, 'foodtruck_bake.pid')
+        with open(pid_file_path, 'w') as fp:
+            fp.write(str(proc.pid))
+
+        logger.debug("Running bake monitor for PID %d" % proc.pid)
+        self._bake_thread = _BakeThread(self.name, self.root_dir, proc,
+                                        self._onBakeEnd)
+        self._bake_thread.start()
+
+    def _onBakeEnd(self):
+        os.unlink(os.path.join(self.root_dir, 'foodtruck_bake.pid'))
+        self._bake_thread = None
+
+
+class _BakeThread(threading.Thread):
+    def __init__(self, sitename, siteroot, proc, callback):
+        super(_BakeThread, self).__init__(
+                name='%s_bake' % sitename, daemon=True)
+        self.sitename = sitename
+        self.siteroot = siteroot
+        self.proc = proc
+        self.callback = callback
+
+        log_file_path = os.path.join(self.siteroot, 'foodtruck_bake.log')
+        self.log_fp = open(log_file_path, 'w', encoding='utf8')
+
+    def run(self):
+        for line in self.proc.stdout:
+            self.log_fp.write(line.decode('utf8'))
+        for line in self.proc.stderr:
+            self.log_fp.write(line.decode('utf8'))
+        self.proc.communicate()
+        if self.proc.returncode != 0:
+            self.log_fp.write("Error, bake process returned code %d" %
+                              self.proc.returncode)
+        self.log_fp.close()
+
+        logger.debug("Bake ended for %s." % self.sitename)
+        self.callback()
+
+
+class FoodTruckSites():
+    def __init__(self, config, current_site=None):
+        self._sites = {}
+        self._site_dirs = {}
+        self.config = config
+        self.current_site = current_site
+
+    def get_root_dir(self, name=None):
+        name = name or self.current_site
+        s = self._site_dirs.get(name)
+        if s:
+            return s
+
+        scfg = self.config.get('sites/%s' % name)
+        root_dir = scfg.get('path')
+        if root_dir is None:
+            raise Exception("Site '%s' has no path defined." % name)
+        if not os.path.isdir(root_dir):
+            raise Exception("Site '%s' has an invalid path." % name)
+        self._site_dirs[name] = root_dir
+        return root_dir
+
+    def get(self, name=None):
+        name = name or self.current_site
+        s = self._sites.get(name)
+        if s:
+            return s
+
+        root_dir = self.get_root_dir(name)
+        s = Site(name, root_dir, self.config)
+        self._sites[name] = s
+        return s
+