Merge #69 Move messaging into cli options and simplify it
This commit is contained in:
commit
77aca8a00a
@ -26,7 +26,6 @@ locale.setlocale(locale.LC_ALL, "C")
|
|||||||
|
|
||||||
|
|
||||||
COMPOSE = None
|
COMPOSE = None
|
||||||
NOTIFIER = None
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -116,6 +115,10 @@ def main():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="output version information and exit",
|
help="output version information and exit",
|
||||||
)
|
)
|
||||||
|
parser.add_option(
|
||||||
|
"--notification-script",
|
||||||
|
help="script for sending progress notification messages"
|
||||||
|
)
|
||||||
|
|
||||||
opts, args = parser.parse_args()
|
opts, args = parser.parse_args()
|
||||||
|
|
||||||
@ -162,6 +165,7 @@ def main():
|
|||||||
parser.error(str(ex))
|
parser.error(str(ex))
|
||||||
|
|
||||||
from pungi.compose import Compose
|
from pungi.compose import Compose
|
||||||
|
import pungi.notifier
|
||||||
|
|
||||||
logger = logging.Logger("Pungi")
|
logger = logging.Logger("Pungi")
|
||||||
kobo.log.add_stderr_logger(logger)
|
kobo.log.add_stderr_logger(logger)
|
||||||
@ -174,8 +178,19 @@ def main():
|
|||||||
else:
|
else:
|
||||||
compose_dir = opts.compose_dir
|
compose_dir = opts.compose_dir
|
||||||
|
|
||||||
compose = Compose(conf, topdir=compose_dir, debug=opts.debug_mode, skip_phases=opts.skip_phase, just_phases=opts.just_phase,
|
notifier = pungi.notifier.PungiNotifier(opts.notification_script)
|
||||||
old_composes=opts.old_composes, koji_event=opts.koji_event, supported=opts.supported, logger=logger)
|
|
||||||
|
compose = Compose(conf,
|
||||||
|
topdir=compose_dir,
|
||||||
|
debug=opts.debug_mode,
|
||||||
|
skip_phases=opts.skip_phase,
|
||||||
|
just_phases=opts.just_phase,
|
||||||
|
old_composes=opts.old_composes,
|
||||||
|
koji_event=opts.koji_event,
|
||||||
|
supported=opts.supported,
|
||||||
|
logger=logger,
|
||||||
|
notifier=notifier)
|
||||||
|
notifier.compose = compose
|
||||||
kobo.log.add_file_logger(logger, compose.paths.log.log_file("global", "pungi.log"))
|
kobo.log.add_file_logger(logger, compose.paths.log.log_file("global", "pungi.log"))
|
||||||
COMPOSE = compose
|
COMPOSE = compose
|
||||||
try:
|
try:
|
||||||
@ -188,19 +203,10 @@ def main():
|
|||||||
def run_compose(compose):
|
def run_compose(compose):
|
||||||
import pungi.phases
|
import pungi.phases
|
||||||
import pungi.metadata
|
import pungi.metadata
|
||||||
import pungi.notifier
|
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
# initializer notifier
|
|
||||||
compose.notifier = pungi.notifier.PungiNotifier(compose)
|
|
||||||
try:
|
|
||||||
compose.notifier.validate()
|
|
||||||
except ValueError as ex:
|
|
||||||
errors.extend(["NOTIFIER: %s" % m for m in ex.message.split('\n')])
|
|
||||||
|
|
||||||
compose.write_status("STARTED")
|
compose.write_status("STARTED")
|
||||||
compose.notifier.send('start')
|
|
||||||
compose.log_info("Host: %s" % socket.gethostname())
|
compose.log_info("Host: %s" % socket.gethostname())
|
||||||
compose.log_info("User name: %s" % getpass.getuser())
|
compose.log_info("User name: %s" % getpass.getuser())
|
||||||
compose.log_info("Working directory: %s" % os.getcwd())
|
compose.log_info("Working directory: %s" % os.getcwd())
|
||||||
@ -241,7 +247,6 @@ def run_compose(compose):
|
|||||||
errors.append("%s: %s" % (phase.name.upper(), i))
|
errors.append("%s: %s" % (phase.name.upper(), i))
|
||||||
if errors:
|
if errors:
|
||||||
for i in errors:
|
for i in errors:
|
||||||
compose.notifier.send('abort')
|
|
||||||
compose.log_error(i)
|
compose.log_error(i)
|
||||||
print(i)
|
print(i)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@ -331,7 +336,6 @@ def run_compose(compose):
|
|||||||
|
|
||||||
compose.log_info("Compose finished: %s" % compose.topdir)
|
compose.log_info("Compose finished: %s" % compose.topdir)
|
||||||
compose.write_status("FINISHED")
|
compose.write_status("FINISHED")
|
||||||
compose.notifier.send('finish')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@ -346,8 +350,6 @@ if __name__ == "__main__":
|
|||||||
COMPOSE.write_status("DOOMED")
|
COMPOSE.write_status("DOOMED")
|
||||||
import kobo.tback
|
import kobo.tback
|
||||||
open(tb_path, "w").write(kobo.tback.Traceback().get_traceback())
|
open(tb_path, "w").write(kobo.tback.Traceback().get_traceback())
|
||||||
if COMPOSE.notifier:
|
|
||||||
COMPOSE.notifier.send('doomed')
|
|
||||||
else:
|
else:
|
||||||
print("Exception: %s" % ex)
|
print("Exception: %s" % ex)
|
||||||
raise
|
raise
|
||||||
|
@ -551,6 +551,7 @@ Translate Paths Settings
|
|||||||
Example config
|
Example config
|
||||||
--------------
|
--------------
|
||||||
::
|
::
|
||||||
|
|
||||||
translate_paths = [
|
translate_paths = [
|
||||||
("/mnt/a", "http://b/dir"),
|
("/mnt/a", "http://b/dir"),
|
||||||
]
|
]
|
||||||
@ -558,42 +559,7 @@ Example config
|
|||||||
Example usage
|
Example usage
|
||||||
-------------
|
-------------
|
||||||
::
|
::
|
||||||
|
|
||||||
>>> from pungi.paths import translate_paths
|
>>> from pungi.paths import translate_paths
|
||||||
>>> print translate_paths(compose_object_with_mapping, "/mnt/a/c/somefile")
|
>>> print translate_paths(compose_object_with_mapping, "/mnt/a/c/somefile")
|
||||||
http://b/dir/c/somefile
|
http://b/dir/c/somefile
|
||||||
|
|
||||||
|
|
||||||
Progress notification
|
|
||||||
=====================
|
|
||||||
|
|
||||||
*Pungi* has the ability to emit notification messages about progress and
|
|
||||||
status. These can be used to e.g. send messages to *fedmsg*. This is
|
|
||||||
implemented by actually calling a separate script.
|
|
||||||
|
|
||||||
The script will be called with one argument describing action that just
|
|
||||||
happened. A JSON-encoded object will be passed to standard input to provide
|
|
||||||
more information about the event. At least, the object will contain a
|
|
||||||
``compose_id`` key.
|
|
||||||
|
|
||||||
Currently these messages are sent:
|
|
||||||
|
|
||||||
* ``start`` -- when composing starts
|
|
||||||
* ``abort`` -- when compose is aborted due to incorrect configuration
|
|
||||||
* ``finish`` -- on successful finish of compose
|
|
||||||
* ``doomed`` -- when an error happens
|
|
||||||
* ``phase-start`` -- on start of a phase
|
|
||||||
* ``phase-stop`` -- when phase is finished
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Config options
|
|
||||||
--------------
|
|
||||||
|
|
||||||
**notification_script**
|
|
||||||
(*str*) -- executable to be invoked to send the message
|
|
||||||
|
@ -15,6 +15,7 @@ Contents:
|
|||||||
contributing
|
contributing
|
||||||
testing
|
testing
|
||||||
configuration
|
configuration
|
||||||
|
messaging
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
|
39
doc/messaging.rst
Normal file
39
doc/messaging.rst
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
.. _messaging:
|
||||||
|
|
||||||
|
Progress notification
|
||||||
|
=====================
|
||||||
|
|
||||||
|
*Pungi* has the ability to emit notification messages about progress and
|
||||||
|
general status of the compose. These can be used to e.g. send messages to
|
||||||
|
*fedmsg*. This is implemented by actually calling a separate script.
|
||||||
|
|
||||||
|
The script will be called with one argument describing action that just
|
||||||
|
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.
|
||||||
|
|
||||||
|
Currently these messages are sent:
|
||||||
|
|
||||||
|
* ``status-change`` -- when composing starts, finishes or fails; a ``status``
|
||||||
|
key is provided to indicate details
|
||||||
|
* ``phase-start`` -- on start of a phase
|
||||||
|
* ``phase-stop`` -- when phase is finished
|
||||||
|
* ``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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Setting it up
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The script should be provided as a command line argument
|
||||||
|
``--notification-script``. ::
|
||||||
|
|
||||||
|
--notification_script=pungi-fedmsg-notification
|
@ -92,7 +92,7 @@ def get_compose_dir(topdir, conf, compose_type="production", compose_date=None,
|
|||||||
|
|
||||||
|
|
||||||
class Compose(kobo.log.LoggingBase):
|
class Compose(kobo.log.LoggingBase):
|
||||||
def __init__(self, conf, topdir, debug=False, skip_phases=None, just_phases=None, old_composes=None, koji_event=None, supported=False, logger=None):
|
def __init__(self, conf, topdir, debug=False, skip_phases=None, just_phases=None, old_composes=None, koji_event=None, supported=False, logger=None, notifier=None):
|
||||||
kobo.log.LoggingBase.__init__(self, logger)
|
kobo.log.LoggingBase.__init__(self, logger)
|
||||||
# TODO: check if minimal conf values are set
|
# TODO: check if minimal conf values are set
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
@ -102,7 +102,7 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
self.just_phases = just_phases or []
|
self.just_phases = just_phases or []
|
||||||
self.old_composes = old_composes or []
|
self.old_composes = old_composes or []
|
||||||
self.koji_event = koji_event
|
self.koji_event = koji_event
|
||||||
self.notifier = None
|
self.notifier = notifier
|
||||||
|
|
||||||
# intentionally upper-case (visible in the code)
|
# intentionally upper-case (visible in the code)
|
||||||
self.DEBUG = debug
|
self.DEBUG = debug
|
||||||
@ -241,6 +241,7 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
self.log_error(msg)
|
self.log_error(msg)
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
open(os.path.join(self.topdir, "STATUS"), "w").write(stat_msg + "\n")
|
open(os.path.join(self.topdir, "STATUS"), "w").write(stat_msg + "\n")
|
||||||
|
self.notifier.send('status-change', status=stat_msg)
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
path = os.path.join(self.topdir, "STATUS")
|
path = os.path.join(self.topdir, "STATUS")
|
||||||
|
@ -17,7 +17,6 @@ import json
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from kobo import shortcuts
|
from kobo import shortcuts
|
||||||
from pungi.checks import validate_options
|
|
||||||
|
|
||||||
|
|
||||||
class PungiNotifier(object):
|
class PungiNotifier(object):
|
||||||
@ -27,23 +26,10 @@ class PungiNotifier(object):
|
|||||||
script fails, a warning will be logged, but the compose process will not be
|
script fails, a warning will be logged, but the compose process will not be
|
||||||
interrupted.
|
interrupted.
|
||||||
"""
|
"""
|
||||||
config_options = (
|
def __init__(self, cmd):
|
||||||
{
|
self.cmd = cmd
|
||||||
"name": "notification_script",
|
|
||||||
"expected_types": [str],
|
|
||||||
"optional": True
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, compose):
|
|
||||||
self.compose = compose
|
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
errors = validate_options(self.compose.conf, self.config_options)
|
|
||||||
if errors:
|
|
||||||
raise ValueError("\n".join(errors))
|
|
||||||
|
|
||||||
def _update_args(self, data):
|
def _update_args(self, data):
|
||||||
"""Add compose related information to the data."""
|
"""Add compose related information to the data."""
|
||||||
data.setdefault('compose_id', self.compose.compose_id)
|
data.setdefault('compose_id', self.compose.compose_id)
|
||||||
@ -58,14 +44,13 @@ class PungiNotifier(object):
|
|||||||
Unless you specify it manually, a ``compose_id`` key with appropriate
|
Unless you specify it manually, a ``compose_id`` key with appropriate
|
||||||
value will be automatically added.
|
value will be automatically added.
|
||||||
"""
|
"""
|
||||||
script = self.compose.conf.get('notification_script', None)
|
if not self.cmd:
|
||||||
if not script:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self._update_args(kwargs)
|
self._update_args(kwargs)
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
ret, _ = shortcuts.run((script, msg),
|
ret, _ = shortcuts.run((self.cmd, msg),
|
||||||
stdin_data=json.dumps(kwargs),
|
stdin_data=json.dumps(kwargs),
|
||||||
can_fail=True,
|
can_fail=True,
|
||||||
workdir=self.compose.paths.compose.topdir(),
|
workdir=self.compose.paths.compose.topdir(),
|
||||||
|
@ -9,4 +9,4 @@ pungi-koji \
|
|||||||
--target-dir=_composes \
|
--target-dir=_composes \
|
||||||
--old-composes=_composes \
|
--old-composes=_composes \
|
||||||
--config=data/dummy-pungi.conf \
|
--config=data/dummy-pungi.conf \
|
||||||
--test
|
--test "$@"
|
||||||
|
@ -13,21 +13,10 @@ from pungi.notifier import PungiNotifier
|
|||||||
|
|
||||||
|
|
||||||
class TestNotifier(unittest.TestCase):
|
class TestNotifier(unittest.TestCase):
|
||||||
def test_incorrect_config(self):
|
|
||||||
compose = mock.Mock(
|
|
||||||
conf={'notification_script': [1, 2]}
|
|
||||||
)
|
|
||||||
|
|
||||||
n = PungiNotifier(compose)
|
|
||||||
with self.assertRaises(ValueError) as err:
|
|
||||||
n.validate()
|
|
||||||
self.assertIn('notification_script', err.message)
|
|
||||||
|
|
||||||
@mock.patch('kobo.shortcuts.run')
|
@mock.patch('kobo.shortcuts.run')
|
||||||
def test_invokes_script(self, run):
|
def test_invokes_script(self, run):
|
||||||
compose = mock.Mock(
|
compose = mock.Mock(
|
||||||
compose_id='COMPOSE_ID',
|
compose_id='COMPOSE_ID',
|
||||||
conf={'notification_script': 'run-notify'},
|
|
||||||
paths=mock.Mock(
|
paths=mock.Mock(
|
||||||
compose=mock.Mock(
|
compose=mock.Mock(
|
||||||
topdir=mock.Mock(return_value='/a/b')
|
topdir=mock.Mock(return_value='/a/b')
|
||||||
@ -37,7 +26,8 @@ class TestNotifier(unittest.TestCase):
|
|||||||
|
|
||||||
run.return_value = (0, None)
|
run.return_value = (0, None)
|
||||||
|
|
||||||
n = PungiNotifier(compose)
|
n = PungiNotifier('run-notify')
|
||||||
|
n.compose = compose
|
||||||
data = {'foo': 'bar', 'baz': 'quux'}
|
data = {'foo': 'bar', 'baz': 'quux'}
|
||||||
n.send('cmd', **data)
|
n.send('cmd', **data)
|
||||||
|
|
||||||
@ -48,9 +38,7 @@ class TestNotifier(unittest.TestCase):
|
|||||||
|
|
||||||
@mock.patch('kobo.shortcuts.run')
|
@mock.patch('kobo.shortcuts.run')
|
||||||
def test_does_not_run_without_config(self, run):
|
def test_does_not_run_without_config(self, run):
|
||||||
compose = mock.Mock(conf={})
|
n = PungiNotifier(None)
|
||||||
|
|
||||||
n = PungiNotifier(compose)
|
|
||||||
n.send('cmd', foo='bar', baz='quux')
|
n.send('cmd', foo='bar', baz='quux')
|
||||||
self.assertFalse(run.called)
|
self.assertFalse(run.called)
|
||||||
|
|
||||||
@ -59,7 +47,6 @@ class TestNotifier(unittest.TestCase):
|
|||||||
compose = mock.Mock(
|
compose = mock.Mock(
|
||||||
compose_id='COMPOSE_ID',
|
compose_id='COMPOSE_ID',
|
||||||
log_warning=mock.Mock(),
|
log_warning=mock.Mock(),
|
||||||
conf={'notification_script': 'run-notify'},
|
|
||||||
paths=mock.Mock(
|
paths=mock.Mock(
|
||||||
compose=mock.Mock(
|
compose=mock.Mock(
|
||||||
topdir=mock.Mock(return_value='/a/b')
|
topdir=mock.Mock(return_value='/a/b')
|
||||||
@ -69,7 +56,8 @@ class TestNotifier(unittest.TestCase):
|
|||||||
|
|
||||||
run.return_value = (1, None)
|
run.return_value = (1, None)
|
||||||
|
|
||||||
n = PungiNotifier(compose)
|
n = PungiNotifier('run-notify')
|
||||||
|
n.compose = compose
|
||||||
n.send('cmd')
|
n.send('cmd')
|
||||||
|
|
||||||
run.assert_called_once_with(('run-notify', 'cmd'),
|
run.assert_called_once_with(('run-notify', 'cmd'),
|
||||||
|
Loading…
Reference in New Issue
Block a user