Mercurial > piecrust2
comparison foodtruck/sites.py @ 602:c6bc0ef03f82
admin: Better UI for publishing websites.
* Support multiple publish targets.
* Dedicated UI for publishing.
* Some UI polish.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Wed, 27 Jan 2016 18:02:25 -0800 |
parents | effbc78b5528 |
children | efc1dc916e7c |
comparison
equal
deleted
inserted
replaced
601:effbc78b5528 | 602:c6bc0ef03f82 |
---|---|
4 import shlex | 4 import shlex |
5 import logging | 5 import logging |
6 import threading | 6 import threading |
7 import subprocess | 7 import subprocess |
8 from piecrust.app import PieCrust | 8 from piecrust.app import PieCrust |
9 from piecrust.configuration import merge_dicts | 9 from piecrust.configuration import merge_dicts, Configuration |
10 | 10 |
11 | 11 |
12 logger = logging.getLogger(__name__) | 12 logger = logging.getLogger(__name__) |
13 | 13 |
14 | 14 |
22 | 22 |
23 class Site(object): | 23 class Site(object): |
24 def __init__(self, name, root_dir, config): | 24 def __init__(self, name, root_dir, config): |
25 self.name = name | 25 self.name = name |
26 self.root_dir = root_dir | 26 self.root_dir = root_dir |
27 self.config = config | 27 self.config = Configuration(values=config.get('sites/%s' % name, {})) |
28 self._global_config = config | |
28 self._piecrust_app = None | 29 self._piecrust_app = None |
29 self._scm = None | 30 self._scm = None |
30 self._bake_thread = None | 31 self._publish_thread = None |
31 logger.debug("Creating site object for %s" % self.name) | 32 logger.debug("Creating site object for %s" % self.name) |
32 | 33 |
33 @property | 34 @property |
34 def piecrust_app(self): | 35 def piecrust_app(self): |
35 if self._piecrust_app is None: | 36 if self._piecrust_app is None: |
39 return self._piecrust_app | 40 return self._piecrust_app |
40 | 41 |
41 @property | 42 @property |
42 def scm(self): | 43 def scm(self): |
43 if self._scm is None: | 44 if self._scm is None: |
44 cfg = None | 45 cfg = copy.deepcopy(self._global_config.get('scm', {})) |
45 scm_cfg = self.config.get('sites/%s/scm' % self.name) | 46 merge_dicts(cfg, self.config.get('scm', {})) |
46 global_scm_cfg = self.config.get('scm') | |
47 if scm_cfg: | |
48 if global_scm_cfg: | |
49 cfg = copy.deepcopy(global_scm_cfg) | |
50 merge_dicts(cfg, scm_cfg) | |
51 else: | |
52 cfg = copy.deepcopy(scm_cfg) | |
53 elif global_scm_cfg: | |
54 cfg = copy.deepcopy(global_scm_cfg) | |
55 | 47 |
56 if not cfg or 'type' not in cfg: | 48 if os.path.isdir(os.path.join(self.root_dir, '.hg')): |
57 raise Exception("No SCM available for site: %s" % self.name) | |
58 | |
59 if cfg['type'] == 'hg': | |
60 from .scm.mercurial import MercurialSourceControl | 49 from .scm.mercurial import MercurialSourceControl |
61 self._scm = MercurialSourceControl(self.root_dir, cfg) | 50 self._scm = MercurialSourceControl(self.root_dir, cfg) |
51 elif os.path.isdir(os.path.join(self.root_dir, '.git')): | |
52 from .scm.git import GitSourceControl | |
53 self._scm = GitSourceControl(self.root_dir, cfg) | |
62 else: | 54 else: |
63 raise NotImplementedError() | 55 self._scm = False |
64 | 56 |
65 return self._scm | 57 return self._scm |
66 | 58 |
67 @property | 59 @property |
68 def is_bake_running(self): | 60 def is_publish_running(self): |
69 return self._bake_thread is not None and self._bake_thread.is_alive() | 61 return (self._publish_thread is not None and |
62 self._publish_thread.is_alive()) | |
70 | 63 |
71 @property | 64 @property |
72 def bake_thread(self): | 65 def publish_thread(self): |
73 return self._bake_thread | 66 return self._publish_thread |
74 | 67 |
75 def bake(self): | 68 def publish(self, target): |
76 bake_cmd = self.config.get('triggers/bake') | 69 target_cfg = self.config.get('publish/%s' % target) |
77 bake_args = shlex.split(bake_cmd) | 70 if not target_cfg: |
71 raise Exception("No such publish target: %s" % target) | |
78 | 72 |
79 logger.debug("Running bake: %s" % bake_args) | 73 target_cmd = target_cfg.get('cmd') |
80 proc = subprocess.Popen(bake_args, cwd=self.root_dir, | 74 if not target_cmd: |
75 raise Exception("No command specified for publish target: %s" % | |
76 target) | |
77 publish_args = shlex.split(target_cmd) | |
78 | |
79 logger.debug( | |
80 "Executing publish target '%s': %s" % (target, publish_args)) | |
81 proc = subprocess.Popen(publish_args, cwd=self.root_dir, | |
81 stdout=subprocess.PIPE, | 82 stdout=subprocess.PIPE, |
82 stderr=subprocess.PIPE) | 83 stderr=subprocess.PIPE) |
83 | 84 |
84 pid_file_path = os.path.join(self.root_dir, 'foodtruck_bake.pid') | 85 pid_file_path = os.path.join(self.root_dir, '.ft_pub.pid') |
85 with open(pid_file_path, 'w') as fp: | 86 with open(pid_file_path, 'w') as fp: |
86 fp.write(str(proc.pid)) | 87 fp.write(str(proc.pid)) |
87 | 88 |
88 logger.debug("Running bake monitor for PID %d" % proc.pid) | 89 logger.debug("Running publishing monitor for PID %d" % proc.pid) |
89 self._bake_thread = _BakeThread(self.name, self.root_dir, proc, | 90 self._publish_thread = _PublishThread( |
90 self._onBakeEnd) | 91 self.name, self.root_dir, proc, self._onPublishEnd) |
91 self._bake_thread.start() | 92 self._publish_thread.start() |
92 | 93 |
93 def _onBakeEnd(self): | 94 def _onPublishEnd(self): |
94 os.unlink(os.path.join(self.root_dir, 'foodtruck_bake.pid')) | 95 os.unlink(os.path.join(self.root_dir, '.ft_pub.pid')) |
95 self._bake_thread = None | 96 self._publish_thread = None |
96 | 97 |
97 | 98 |
98 class _BakeThread(threading.Thread): | 99 class _PublishThread(threading.Thread): |
99 def __init__(self, sitename, siteroot, proc, callback): | 100 def __init__(self, sitename, siteroot, proc, callback): |
100 super(_BakeThread, self).__init__( | 101 super(_PublishThread, self).__init__( |
101 name='%s_bake' % sitename, daemon=True) | 102 name='%s_publish' % sitename, daemon=True) |
102 self.sitename = sitename | 103 self.sitename = sitename |
103 self.siteroot = siteroot | 104 self.siteroot = siteroot |
104 self.proc = proc | 105 self.proc = proc |
105 self.callback = callback | 106 self.callback = callback |
106 | 107 |
107 log_file_path = os.path.join(self.siteroot, 'foodtruck_bake.log') | 108 log_file_path = os.path.join(self.siteroot, '.ft_pub.log') |
108 self.log_fp = open(log_file_path, 'w', encoding='utf8') | 109 self.log_fp = open(log_file_path, 'w', encoding='utf8') |
109 | 110 |
110 def run(self): | 111 def run(self): |
111 for line in self.proc.stdout: | 112 for line in self.proc.stdout: |
112 self.log_fp.write(line.decode('utf8')) | 113 self.log_fp.write(line.decode('utf8')) |
113 for line in self.proc.stderr: | 114 for line in self.proc.stderr: |
114 self.log_fp.write(line.decode('utf8')) | 115 self.log_fp.write(line.decode('utf8')) |
115 self.proc.communicate() | 116 self.proc.communicate() |
116 if self.proc.returncode != 0: | 117 if self.proc.returncode != 0: |
117 self.log_fp.write("Error, bake process returned code %d" % | 118 self.log_fp.write("Error, publish process returned code %d" % |
118 self.proc.returncode) | 119 self.proc.returncode) |
119 self.log_fp.close() | 120 self.log_fp.close() |
120 | 121 |
121 logger.debug("Bake ended for %s." % self.sitename) | 122 logger.debug("Publish ended for %s." % self.sitename) |
122 self.callback() | 123 self.callback() |
123 | 124 |
124 | 125 |
125 class FoodTruckSites(): | 126 class FoodTruckSites(): |
126 def __init__(self, config, current_site): | 127 def __init__(self, config, current_site): |