comparison foodtruck/sites.py @ 613:e2e955a3bb25

publish: Add publish command. * Add `shell` publisher. * Refactor admin panel's publishing backend to use that, along with the new PID file support.
author Ludovic Chabant <ludovic@chabant.com>
date Thu, 04 Feb 2016 08:05:03 -0800
parents efc1dc916e7c
children
comparison
equal deleted inserted replaced
612:2edaefcb82cd 613:e2e955a3bb25
1 import os 1 import os
2 import os.path 2 import os.path
3 import copy 3 import copy
4 import shlex
5 import logging 4 import logging
6 import threading 5 import threading
7 import subprocess 6 import subprocess
8 from piecrust.app import PieCrust 7 from piecrust.app import PieCrust
9 from piecrust.configuration import merge_dicts 8 from piecrust.configuration import merge_dicts
25 self.name = name 24 self.name = name
26 self.root_dir = root_dir 25 self.root_dir = root_dir
27 self._global_config = config 26 self._global_config = config
28 self._piecrust_app = None 27 self._piecrust_app = None
29 self._scm = None 28 self._scm = None
30 self._publish_thread = None
31 logger.debug("Creating site object for %s" % self.name) 29 logger.debug("Creating site object for %s" % self.name)
32 30
33 @property 31 @property
34 def piecrust_app(self): 32 def piecrust_app(self):
35 if self._piecrust_app is None: 33 if self._piecrust_app is None:
54 self._scm = False 52 self._scm = False
55 53
56 return self._scm 54 return self._scm
57 55
58 @property 56 @property
59 def is_publish_running(self): 57 def publish_pid_file(self):
60 return (self._publish_thread is not None and 58 return os.path.join(self.piecrust_app.cache_dir, 'publish.pid')
61 self._publish_thread.is_alive())
62 59
63 @property 60 @property
64 def publish_thread(self): 61 def publish_log_file(self):
65 return self._publish_thread 62 return os.path.join(self.piecrust_app.cache_dir, 'publish.log')
66 63
67 def publish(self, target): 64 def publish(self, target):
68 target_cfg = self.piecrust_app.config.get('publish/%s' % target) 65 args = [
69 if not target_cfg: 66 'chef',
70 raise Exception("No such publish target: %s" % target) 67 '--pid-file', self.publish_pid_file,
68 'publish', target,
69 '--log-publisher', self.publish_log_file]
70 proc = subprocess.Popen(args, cwd=self.root_dir)
71 71
72 target_cmd = target_cfg.get('cmd') 72 def _comm():
73 if not target_cmd: 73 proc.communicate()
74 raise Exception("No command specified for publish target: %s" %
75 target)
76 publish_args = shlex.split(target_cmd)
77 74
78 logger.debug( 75 t = threading.Thread(target=_comm, daemon=True)
79 "Executing publish target '%s': %s" % (target, publish_args)) 76 t.start()
80 proc = subprocess.Popen(publish_args, cwd=self.root_dir,
81 stdout=subprocess.PIPE,
82 stderr=subprocess.PIPE)
83
84 pid_file_path = os.path.join(self.root_dir, '.ft_pub.pid')
85 with open(pid_file_path, 'w') as fp:
86 fp.write(str(proc.pid))
87
88 logger.debug("Running publishing monitor for PID %d" % proc.pid)
89 self._publish_thread = _PublishThread(
90 self.name, self.root_dir, proc, self._onPublishEnd)
91 self._publish_thread.start()
92
93 def _onPublishEnd(self):
94 os.unlink(os.path.join(self.root_dir, '.ft_pub.pid'))
95 self._publish_thread = None
96
97
98 class _PublishThread(threading.Thread):
99 def __init__(self, sitename, siteroot, proc, callback):
100 super(_PublishThread, self).__init__(
101 name='%s_publish' % sitename, daemon=True)
102 self.sitename = sitename
103 self.siteroot = siteroot
104 self.proc = proc
105 self.callback = callback
106
107 log_file_path = os.path.join(self.siteroot, '.ft_pub.log')
108 self.log_fp = open(log_file_path, 'w', encoding='utf8')
109
110 def run(self):
111 for line in self.proc.stdout:
112 self.log_fp.write(line.decode('utf8'))
113 for line in self.proc.stderr:
114 self.log_fp.write(line.decode('utf8'))
115 self.proc.communicate()
116 if self.proc.returncode != 0:
117 self.log_fp.write("Error, publish process returned code %d" %
118 self.proc.returncode)
119 self.log_fp.close()
120
121 logger.debug("Publish ended for %s." % self.sitename)
122 self.callback()
123 77
124 78
125 class FoodTruckSites(): 79 class FoodTruckSites():
126 def __init__(self, config, current_site): 80 def __init__(self, config, current_site):
127 self._sites = {} 81 self._sites = {}