comparison piecrust/publishing/publisher.py @ 621:8f9c0bdb3724

publish: Polish/refactor the publishing workflows. * Add a `--preview` option. * The `--list` option gives a nicer output, and prints warnings/errors for incorrect configuration. * Moved most of the `shell` code into a base class that's reusable. * Simplified the code to log publishing to a file. * Nicer output overall, with times.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 08 Feb 2016 20:44:26 -0800
parents e2e955a3bb25
children 6abb436fea5b
comparison
equal deleted inserted replaced
620:c2708f20a87b 621:8f9c0bdb3724
1 import os.path
2 import time
1 import logging 3 import logging
4 import urllib.parse
5 from piecrust.chefutil import format_timed
2 from piecrust.publishing.base import PublishingContext 6 from piecrust.publishing.base import PublishingContext
3 7
4 8
5 logger = logging.getLogger(__name__) 9 logger = logging.getLogger(__name__)
6 10
15 19
16 class Publisher(object): 20 class Publisher(object):
17 def __init__(self, app): 21 def __init__(self, app):
18 self.app = app 22 self.app = app
19 23
20 def run(self, target, log_file=None): 24 def run(self, target, preview=False, log_file=None):
25 start_time = time.perf_counter()
26
27 # Get the configuration for this target.
21 target_cfg = self.app.config.get('publish/%s' % target) 28 target_cfg = self.app.config.get('publish/%s' % target)
22 if not target_cfg: 29 if not target_cfg:
23 raise InvalidPublishTargetError( 30 raise InvalidPublishTargetError(
24 "No such publish target: %s" % target) 31 "No such publish target: %s" % target)
25 32
26 target_type = target_cfg.get('type') 33 target_type = None
27 if not target_type: 34 bake_first = True
28 raise InvalidPublishTargetError( 35 parsed_url = None
29 "Publish target '%s' doesn't specify a type." % target) 36 if isinstance(target_cfg, dict):
37 target_type = target_cfg.get('type')
38 if not target_type:
39 raise InvalidPublishTargetError(
40 "Publish target '%s' doesn't specify a type." % target)
41 bake_first = target_cfg.get('bake', True)
42 elif isinstance(target_cfg, str):
43 comps = urllib.parse.urlparse(target_cfg)
44 if not comps.scheme:
45 raise InvalidPublishTargetError(
46 "Publish target '%s' has an invalid target URL." %
47 target)
48 parsed_url = comps
49 target_type = find_publisher_name(self.app, comps.scheme)
50 if target_type is None:
51 raise InvalidPublishTargetError(
52 "No such publish target scheme: %s" % comps.scheme)
30 53
54 # Setup logging stuff.
55 hdlr = None
56 root_logger = logging.getLogger()
57 if log_file and not preview:
58 logger.debug("Adding file handler for: %s" % log_file)
59 hdlr = logging.FileHandler(log_file, mode='w', encoding='utf8')
60 root_logger.addHandler(hdlr)
61 if not preview:
62 logger.info("Deploying to %s" % target)
63 else:
64 logger.info("Previewing deployment to %s" % target)
65
66 # Bake first is necessary.
67 bake_out_dir = None
68 if bake_first:
69 bake_out_dir = os.path.join(self.app.cache_dir, 'pub', target)
70 if not preview:
71 bake_start_time = time.perf_counter()
72 logger.debug("Baking first to: %s" % bake_out_dir)
73
74 from piecrust.baking.baker import Baker
75 baker = Baker(self.app, bake_out_dir)
76 rec1 = baker.bake()
77
78 from piecrust.processing.pipeline import ProcessorPipeline
79 proc = ProcessorPipeline(self.app, bake_out_dir)
80 rec2 = proc.run()
81
82 if not rec1.success or not rec2.success:
83 raise Exception(
84 "Error during baking, aborting publishing.")
85 logger.info(format_timed(bake_start_time, "Baked website."))
86 else:
87 logger.info("Would bake to: %s" % bake_out_dir)
88
89 # Create the appropriate publisher.
31 pub = None 90 pub = None
32 for pub_cls in self.app.plugin_loader.getPublishers(): 91 for pub_cls in self.app.plugin_loader.getPublishers():
33 if pub_cls.PUBLISHER_NAME == target_type: 92 if pub_cls.PUBLISHER_NAME == target_type:
34 pub = pub_cls(self.app, target) 93 pub = pub_cls(self.app, target)
35 break 94 break
36 if pub is None: 95 if pub is None:
37 raise InvalidPublishTargetError( 96 raise InvalidPublishTargetError(
38 "Publish target '%s' has invalid type: %s" % 97 "Publish target '%s' has invalid type: %s" %
39 (target, target_type)) 98 (target, target_type))
99 pub.parsed_url = parsed_url
100
101 # Publish!
102 logger.debug(
103 "Running publish target '%s' with publisher: %s" %
104 (target, pub.PUBLISHER_NAME))
105 pub_start_time = time.perf_counter()
40 106
41 ctx = PublishingContext() 107 ctx = PublishingContext()
42 108 ctx.bake_out_dir = bake_out_dir
43 hdlr = None 109 ctx.preview = preview
44 if log_file:
45 if not pub.is_using_custom_logging:
46 logger.debug("Adding file handler for: %s" % log_file)
47 hdlr = logging.FileHandler(log_file, mode='w', encoding='utf8')
48 logger.addHandler(hdlr)
49 else:
50 logger.debug("Creating custom log file: %s" % log_file)
51 ctx.custom_logging_file = open(
52 log_file, mode='w', encoding='utf8')
53
54 intro_msg = ("Running publish target '%s' with publisher: %s" %
55 (target, pub.PUBLISHER_NAME))
56 logger.debug(intro_msg)
57 if ctx.custom_logging_file:
58 ctx.custom_logging_file.write(intro_msg + "\n")
59
60 try: 110 try:
61 success = pub.run(ctx) 111 success = pub.run(ctx)
62 except Exception as ex: 112 except Exception as ex:
63 raise PublishingError( 113 raise PublishingError(
64 "Error publishing to target: %s" % target) from ex 114 "Error publishing to target: %s" % target) from ex
65 finally: 115 finally:
66 if ctx.custom_logging_file:
67 ctx.custom_logging_file.close()
68 if hdlr: 116 if hdlr:
69 logger.removeHandler(hdlr) 117 root_logger.removeHandler(hdlr)
70 hdlr.close() 118 hdlr.close()
71 119
72 if not success: 120 if not success:
73 raise PublishingError( 121 raise PublishingError(
74 "Unknown error publishing to target: %s" % target) 122 "Unknown error publishing to target: %s" % target)
123 logger.info(format_timed(
124 pub_start_time, "Ran publisher %s" % pub.PUBLISHER_NAME))
75 125
126 logger.info(format_timed(start_time, 'Deployed to %s' % target))
127
128
129 def find_publisher_class(app, scheme):
130 for pub_cls in app.plugin_loader.getPublishers():
131 pub_sch = getattr(pub_cls, 'PUBLISHER_SCHEME', None)
132 if ('bake+%s' % pub_sch) == scheme:
133 return pub_cls
134 return None
135
136
137 def find_publisher_name(app, scheme):
138 pub_cls = find_publisher_class(app, scheme)
139 if pub_cls:
140 return pub_cls.PUBLISHER_NAME
141 return None
142