diff --git a/bin/pungi-koji b/bin/pungi-koji index d256748d..9dcf5eac 100755 --- a/bin/pungi-koji +++ b/bin/pungi-koji @@ -121,32 +121,43 @@ def main(): ) opts, args = parser.parse_args() + import pungi.notifier + notifier = pungi.notifier.PungiNotifier(opts.notification_script) + + def fail_to_start(msg, **kwargs): + notifier.send('fail-to-start', workdir=opts.target_dir, + command=sys.argv, target_dir=opts.target_dir, + config=opts.config, detail=msg, **kwargs) + + def abort(msg): + fail_to_start(msg) + parser.error(msg) if opts.version: print("pungi %s" % get_full_version()) sys.exit(0) if opts.target_dir and opts.compose_dir: - parser.error("cannot specify --target-dir and --compose-dir at once") + abort("cannot specify --target-dir and --compose-dir at once") if not opts.target_dir and not opts.compose_dir: - parser.error("please specify a target directory") + abort("please specify a target directory") if opts.target_dir and not opts.compose_dir: opts.target_dir = os.path.abspath(opts.target_dir) if not os.path.isdir(opts.target_dir): - parser.error("The target directory does not exist or is not a directory: %s" % opts.target_dir) + abort("The target directory does not exist or is not a directory: %s" % opts.target_dir) else: opts.compose_dir = os.path.abspath(opts.compose_dir) if not os.path.isdir(opts.compose_dir): - parser.error("The compose directory does not exist or is not a directory: %s" % opts.compose_dir) + abort("The compose directory does not exist or is not a directory: %s" % opts.compose_dir) compose_type = opts.compose_type or "production" if compose_type == "production" and not opts.label and not opts.no_label: - parser.error("must specify label for a production compose") + abort("must specify label for a production compose") if not opts.config: - parser.error("please specify a config") + abort("please specify a config") opts.config = os.path.abspath(opts.config) import kobo.conf @@ -157,10 +168,9 @@ def main(): try: productmd.composeinfo.verify_label(opts.label) except ValueError as ex: - parser.error(str(ex)) + abort(str(ex)) from pungi.compose import Compose - import pungi.notifier logger = logging.Logger("Pungi") kobo.log.add_stderr_logger(logger) @@ -177,6 +187,7 @@ def main(): if errors: for error in errors: print >>sys.stderr, error + fail_to_start('Config validation failed', errors=errors) sys.exit(1) if opts.target_dir: @@ -184,8 +195,6 @@ def main(): else: compose_dir = opts.compose_dir - notifier = pungi.notifier.PungiNotifier(opts.notification_script) - compose = Compose(conf, topdir=compose_dir, debug=opts.debug_mode, diff --git a/doc/messaging.rst b/doc/messaging.rst index 1ce9c22d..4d2f1eac 100644 --- a/doc/messaging.rst +++ b/doc/messaging.rst @@ -12,6 +12,9 @@ happened. A JSON-encoded object will be passed to standard input to provide more information about the event. At the very least, the object will contain a ``compose_id`` key. +The script is invoked in compose directory and can read other information +there. + Currently these messages are sent: * ``status-change`` -- when composing starts, finishes or fails; a ``status`` @@ -21,12 +24,12 @@ Currently these messages are sent: * ``createiso-targets`` -- with a list of images to be created * ``createiso-imagedone`` -- when any single image is finished * ``createiso-imagefail`` -- when any single image fails to create + * ``fail-to-start`` -- when there are incorrect CLI options or errors in + configuration file; this message does not contain ``compose_id`` nor is it + started in the compose directory (which does not exist yet) For phase related messages ``phase_name`` key is provided as well. -The script is invoked in compose directory and can read other information -there. - A ``pungi-fedmsg-notification`` script is provided and understands this interface. diff --git a/pungi/notifier.py b/pungi/notifier.py index eab83524..a7d4c369 100644 --- a/pungi/notifier.py +++ b/pungi/notifier.py @@ -30,9 +30,12 @@ class PungiNotifier(object): def __init__(self, cmd): self.cmd = cmd self.lock = threading.Lock() + self.compose = None def _update_args(self, data): """Add compose related information to the data.""" + if not self.compose: + return data.setdefault('compose_id', self.compose.compose_id) # Publish where in the world this compose will end up living @@ -40,7 +43,7 @@ class PungiNotifier(object): self.compose, self.compose.paths.compose.topdir()) data.setdefault('location', location) - def send(self, msg, **kwargs): + def send(self, msg, workdir=None, **kwargs): """Send a message. The actual meaning of ``msg`` depends on what the notification script @@ -55,13 +58,18 @@ class PungiNotifier(object): self._update_args(kwargs) + if self.compose: + workdir = self.compose.paths.compose.topdir() + with self.lock: - self.compose.log_debug("Notification: %r %r, %r" % ( - self.cmd, msg, kwargs)) + if self.compose: + self.compose.log_debug("Notification: %r %r, %r" % ( + self.cmd, msg, kwargs)) ret, _ = shortcuts.run((self.cmd, msg), stdin_data=json.dumps(kwargs), can_fail=True, - workdir=self.compose.paths.compose.topdir(), + workdir=workdir, return_stdout=False) if ret != 0: - self.compose.log_warning('Failed to invoke notification script.') + if self.compose: + self.compose.log_warning('Failed to invoke notification script.')