summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2018-04-30 17:24:27 +0300
committerLars Wirzenius <liw@liw.fi>2018-06-10 19:44:48 +0300
commitfd3fd66a1db8925e5a9a3d34acdd213f49378877 (patch)
tree5246b011f1cd035417fbca0a361b78fc630b2317
parent9759c2b51a1250aa345c21b7cc6b793f4965ac2d (diff)
downloadick2-fd3fd66a1db8925e5a9a3d34acdd213f49378877.tar.gz
Add: notification service
-rw-r--r--debian/postinst1
-rw-r--r--ick2/__init__.py3
-rw-r--r--ick2/actions.py63
-rw-r--r--ick2/actions_tests.py5
-rw-r--r--ick2/client.py10
-rw-r--r--ick2/controllerapi.py3
-rw-r--r--ick2/notificationapi.py45
-rw-r--r--ick2/sendmail.py92
-rw-r--r--ick2/versionapi.py5
-rw-r--r--ick2/versionapi_tests.py3
-rw-r--r--ick2/workapi.py3
-rw-r--r--ick_controller.py2
-rwxr-xr-xicktool1
-rw-r--r--notification_service.py125
-rw-r--r--notification_service.service29
-rw-r--r--pylint.conf1
-rwxr-xr-xsend-notify-email41
-rw-r--r--setup.py2
-rw-r--r--start_notification_service20
-rw-r--r--without-tests2
-rwxr-xr-xworker_manager11
-rw-r--r--yarns/100-projects.yarn1
-rw-r--r--yarns/150-pipelines.yarn1
-rw-r--r--yarns/200-version.yarn2
-rw-r--r--yarns/300-workers.yarn1
-rw-r--r--yarns/400-build.yarn3
-rw-r--r--yarns/500-build-fail.yarn1
-rw-r--r--yarns/600-unauthz.yarn1
-rw-r--r--yarns/900-local.yarn14
29 files changed, 487 insertions, 4 deletions
diff --git a/debian/postinst b/debian/postinst
index 35578ed..7322fd5 100644
--- a/debian/postinst
+++ b/debian/postinst
@@ -37,6 +37,7 @@ create_user()
create_user _ick /var/lib/ick /var/log/ick
create_user _ickwm /var/lib/ick/wm /var/log/ickwm
create_user _ickas /var/lib/ick/blobs /var/log/ickas
+create_user _ickns /var/lib/ick/blobs /var/log/ickns
# Create controller config dir.
install -d -m 0755 -o root -g root /etc/ick
diff --git a/ick2/__init__.py b/ick2/__init__.py
index a6a4295..fe8027c 100644
--- a/ick2/__init__.py
+++ b/ick2/__init__.py
@@ -71,6 +71,7 @@ from .workerapi import WorkerAPI
from .controllerapi import (
ControllerAPI,
)
+from .notificationapi import NotificationAPI
from .blobapi import BlobAPI
from .blob_store import BlobStore
@@ -101,3 +102,5 @@ from .actions import (
PopulateSystreeAction,
GitAction,
)
+
+from .sendmail import Sendmail
diff --git a/ick2/actions.py b/ick2/actions.py
index 202bcb6..3d77aa5 100644
--- a/ick2/actions.py
+++ b/ick2/actions.py
@@ -35,7 +35,9 @@ class ActionFactory:
'container': ick2.ContainerEnvironment,
}
- def __init__(self, systree, workspace_area, reporter):
+ def __init__(self, build_id, systree, workspace_area, reporter):
+ self._cc = None
+ self._build_id = build_id
self._systree = systree
self._workspace_area = workspace_area
self._reporter = reporter
@@ -46,6 +48,9 @@ class ActionFactory:
def add_env_var(self, name, value): # pragma: no cover
self._extra_env[name] = value
+ def set_controller_client(self, cc): # pragma: no cover
+ self._cc = cc
+
def set_token(self, token):
self._token = token
@@ -79,6 +84,8 @@ class ActionFactory:
def create_action(self, spec, project_name):
env = self.create_environment(spec, project_name)
action = self._create_action_object(env, spec)
+ action.set_controller_client(self._cc)
+ action.set_build_id(self._build_id)
action.set_token(self.get_token())
action.set_blob_url_func(self.get_blob_url_func())
return action
@@ -103,6 +110,7 @@ class ActionFactory:
'git': GitAction,
'rsync': RsyncAction,
'dput': DputAction,
+ 'notify': NotifyAction,
}
kind = spec['action']
klass = rules2.get(kind)
@@ -116,9 +124,23 @@ class Action: # pragma: no cover
def __init__(self, env):
self._env = env
+ self._cc = None
+ self._build_id = None
self._token = None
self._blob_url = None
+ def set_controller_client(self, cc):
+ self._cc = cc
+
+ def get_controller_client(self):
+ return self._cc
+
+ def set_build_id(self, build_id):
+ self._build_id = build_id
+
+ def get_build_id(self):
+ return self._build_id
+
def set_token(self, token):
self._token = token
@@ -444,6 +466,45 @@ class DputAction(Action): # pragma: no cover
return exit_code
+class NotifyAction(Action): # pragma: no cover
+
+ def encode_parameters(self, params):
+ pass
+
+ def execute(self, params, step):
+ env = self.get_env()
+ cc = self.get_controller_client()
+ assert cc is not None
+ build_id = self.get_build_id()
+
+ env.report(None, 'Notifying about build ending\n')
+
+ build_path = '/builds/{}'.format(build_id)
+ build = cc.show(build_path)
+
+ params = build.get('parameters', {})
+ if 'notify' not in params:
+ env.report(
+ 0,
+ 'NOT notifying about build ending: no "notify" parameter.\n')
+ return
+
+ recipients = params['notify']
+
+ log = cc.get_log(build_id)
+ log = log.decode('utf-8')
+
+ notify = {
+ 'recipients': recipients,
+ 'build': build,
+ 'log': log,
+ }
+
+ cc.notify(notify)
+
+ env.report(0, 'Notified about build {} ending\n'.format(build_id))
+
+
def make_directory_empty(env, dirname):
return env.runcmd(
['sudo', 'find', dirname, '-mindepth', '1', '-delete'])
diff --git a/ick2/actions_tests.py b/ick2/actions_tests.py
index 06c07f5..775428f 100644
--- a/ick2/actions_tests.py
+++ b/ick2/actions_tests.py
@@ -28,7 +28,9 @@ class ActionFactoryTests(unittest.TestCase):
self.tempdir = tempfile.mkdtemp()
self.workspace_area = self.tempdir
self.project = 'magic'
- self.af = ick2.ActionFactory('systree', self.workspace_area, None)
+ self.build_id = 'moomin/42'
+ self.af = ick2.ActionFactory(
+ self.build_id, 'systree', self.workspace_area, None)
def tearDown(self):
shutil.rmtree(self.tempdir)
@@ -88,6 +90,7 @@ class ActionFactoryTests(unittest.TestCase):
action = self.af.create_action(spec, self.project)
self.assertTrue(isinstance(action, ick2.ShellAction))
self.assertTrue(isinstance(action.get_env(), ick2.HostEnvironment))
+ self.assertEqual(action.get_build_id(), self.build_id)
self.assertEqual(action.get_token(), token)
self.assertEqual(action.get_blob_upload_url('foo'), 'foo')
diff --git a/ick2/client.py b/ick2/client.py
index 47941b5..e654d29 100644
--- a/ick2/client.py
+++ b/ick2/client.py
@@ -172,6 +172,12 @@ class ControllerClient:
logging.info('Authentication URL: %r', url)
return url
+ def get_notify_url(self): # pragma: no cover
+ version = self.get_version()
+ url = version.get('notify_url')
+ logging.info('Notification URL: %r', url)
+ return url
+
def get_auth_client(self):
url = self.get_auth_url()
ac = AuthClient()
@@ -249,6 +255,10 @@ class ControllerClient:
url = self.url(path)
return self._api.get_blob(url)
+ def notify(self, notify): # pragma: no cover
+ url = self.get_notify_url()
+ self._api.post(url, body=notify)
+
class AuthClient:
diff --git a/ick2/controllerapi.py b/ick2/controllerapi.py
index c2e7eff..1bc830d 100644
--- a/ick2/controllerapi.py
+++ b/ick2/controllerapi.py
@@ -28,6 +28,9 @@ class ControllerAPI:
def set_auth_url(self, url): # pragma: no cover
self._set_url('set_auth_url', url)
+ def set_notify_url(self, url): # pragma: no cover
+ self._set_url('set_notify_url', url)
+
def _set_url(self, what, url): # pragma: no cover
api = self._get_version_api()
if api:
diff --git a/ick2/notificationapi.py b/ick2/notificationapi.py
new file mode 100644
index 0000000..ab261bd
--- /dev/null
+++ b/ick2/notificationapi.py
@@ -0,0 +1,45 @@
+# Copyright 2018 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import ick2
+
+
+class NotificationAPI:
+
+ def __init__(self, config):
+ self._config = config
+
+ def find_missing_route(self, missing_path):
+ return [
+ {
+ 'method': 'POST',
+ 'path': '/notify',
+ 'callback': self.notify,
+ },
+ ]
+
+ def notify(self, content_type, body, **kwargs):
+ ick2.log.log('info', msg_text='Notification requested', kwargs=kwargs)
+
+ recipients = body.get('recipients', [])
+ build = body.get('build', {})
+ log = body.get('log', '')
+
+ sendmail = ick2.Sendmail()
+ sendmail.set_config(self._config)
+ sendmail.send(recipients, build, log)
+
+ return ick2.OK('')
diff --git a/ick2/sendmail.py b/ick2/sendmail.py
new file mode 100644
index 0000000..0b0d34e
--- /dev/null
+++ b/ick2/sendmail.py
@@ -0,0 +1,92 @@
+# Copyright 2018 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import email.message
+import smtplib
+
+
+body_tmpl = """\
+This is a notification from ick, the CI system.
+
+Project: {project}
+Build ID: {build_id}
+Log ID: {log}
+Worker: {worker}
+Status: {status}
+Exit code: {exit_code}
+
+Build log is attached.
+
+"""
+
+
+class Sendmail:
+
+ config_fields = [
+ 'from_addr',
+ 'smtp_server',
+ 'smtp_port',
+ 'smtp_user',
+ 'smtp_password',
+ ]
+
+ def __init__(self):
+ self._config = None
+
+ def set_config(self, config):
+ for field in self.config_fields:
+ assert field in config
+ self._config = config
+
+ def send(self, recipients, build, log):
+ msg = self.construct_msg(recipients, build, log)
+ self._send_msg(recipients, msg)
+
+ def _send_msg(self, recipients, msg):
+ server = smtplib.SMTP()
+ server.connect(
+ host=self._config['smtp_server'], port=self._config['smtp_port'])
+ server.ehlo()
+ server.starttls()
+ server.login(self._config['smtp_user'], self._config['smtp_password'])
+ server.sendmail(self._config['from_addr'], recipients, msg.as_bytes())
+
+ def construct_msg(self, recipients, build, log):
+ msg = email.message.Message()
+ msg.add_header('From', self._config['from_addr'])
+ msg.add_header('To', ', '.join(recipients))
+ msg.add_header('Subject', self.get_subject(build))
+ msg.add_header('Content-Type', 'multipart/mixed')
+ msg.attach(self.get_body(build))
+ msg.attach(self.get_log(log))
+ return msg
+
+ def get_subject(self, build):
+ return 'Ick notification: {build_id} {status}'.format(**build)
+
+ def get_body(self, build):
+ msg = email.message.Message()
+ msg.set_payload(body_tmpl.format(**build))
+ msg.add_header('Content-Type', 'text/plain')
+ return msg
+
+ def get_log(self, log):
+ msg = email.message.Message()
+ msg.set_payload(log)
+ msg.add_header('Content-Type', 'text/plain')
+ msg.add_header(
+ 'Content-Disposition', 'inline', filename='build.log')
+ return msg
diff --git a/ick2/versionapi.py b/ick2/versionapi.py
index c688c1b..cd62ce9 100644
--- a/ick2/versionapi.py
+++ b/ick2/versionapi.py
@@ -22,6 +22,7 @@ class VersionAPI(ick2.APIbase):
super().__init__(state)
self._artifact_store_url = None
self._auth_url = None
+ self._notify_url = None
def set_artifact_store_url(self, url):
self._artifact_store_url = url
@@ -29,6 +30,9 @@ class VersionAPI(ick2.APIbase):
def set_auth_url(self, url):
self._auth_url = url
+ def set_notify_url(self, url):
+ self._notify_url = url
+
def get_routes(self, path): # pragma: no cover
return [
{
@@ -44,6 +48,7 @@ class VersionAPI(ick2.APIbase):
'version': ick2.__version__,
'artifact_store': self._artifact_store_url,
'auth_url': self._auth_url,
+ 'notify_url': self._notify_url,
}
def create(self, body, **kwargs): # pragma: no cover
diff --git a/ick2/versionapi_tests.py b/ick2/versionapi_tests.py
index 32f9808..4f42b29 100644
--- a/ick2/versionapi_tests.py
+++ b/ick2/versionapi_tests.py
@@ -24,9 +24,11 @@ class VersionAPITests(unittest.TestCase):
def test_returns_version_correcly(self):
bloburl = 'https://blobs.example.com'
idpurl = 'https://idp.example.com'
+ notifyurl = 'https://notify.example.com'
api = ick2.VersionAPI(None)
api.set_artifact_store_url(bloburl)
api.set_auth_url(idpurl)
+ api.set_notify_url(notifyurl)
response = api.get_version()
self.assertEqual(
response,
@@ -34,5 +36,6 @@ class VersionAPITests(unittest.TestCase):
'version': ick2.__version__,
'artifact_store': bloburl,
'auth_url': idpurl,
+ 'notify_url': notifyurl,
}
)
diff --git a/ick2/workapi.py b/ick2/workapi.py
index 01f9932..0c885b5 100644
--- a/ick2/workapi.py
+++ b/ick2/workapi.py
@@ -192,12 +192,13 @@ class WorkAPI(ick2.APIbase):
name, doing.get(name), update[name]))
def _append_to_build_log(self, log, update):
+ ick2.log.log('trace', msg_text='appending to build log', update=update)
for stream in ['stdout', 'stderr']:
text = update.get(stream, '')
self._append_text_to_build_log(log, text)
def _append_text_to_build_log(self, log, text):
- log['log'] = log.get('log', '') + text
+ log['log'] = log.get('log', '') + (text or '')
def create(self, body, **kwargs): # pragma: no cover
pass
diff --git a/ick_controller.py b/ick_controller.py
index 108a22a..4c952af 100644
--- a/ick_controller.py
+++ b/ick_controller.py
@@ -48,6 +48,7 @@ default_config = {
'statedir': None,
'artifact-store': None,
'auth-url': None,
+ 'notify-url': None,
}
@@ -97,6 +98,7 @@ def main():
api = ick2.ControllerAPI(state)
api.set_artifact_store_url(config['artifact-store'])
api.set_auth_url(config['auth-url'])
+ api.set_notify_url(config['notify-url'])
ick2.log.log(
'info', msg_text='created ControllerAPI',
artifact_store=config['artifact-store'])
diff --git a/icktool b/icktool
index 2fcdf04..3e954e2 100755
--- a/icktool
+++ b/icktool
@@ -66,6 +66,7 @@ class Icktool(cliapp.Application):
'uapi_projects_id_status_put',
'uapi_blobs_id_get',
'uapi_blobs_id_put',
+ 'uapi_notify_post',
] + scopes_for_types(types)
def add_settings(self):
diff --git a/notification_service.py b/notification_service.py
new file mode 100644
index 0000000..002650d
--- /dev/null
+++ b/notification_service.py
@@ -0,0 +1,125 @@
+#!/usr/bin/python3
+# Copyright (C) 2018 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import logging
+import logging.handlers
+import os
+import sys
+
+
+import apifw
+import slog
+import yaml
+
+
+import ick2
+
+
+transactions = slog.Counter()
+
+
+def counter():
+ return 'HTTP transaction {}'.format(transactions.increment())
+
+
+def dict_logger(log, stack_info=None):
+ ick2.log.log(exc_info=stack_info, **log)
+
+
+default_config = {
+ 'token-public-key': None,
+ 'token-audience': None,
+ 'token-issuer': None,
+ 'log': [],
+ 'from_addr': None,
+ 'smtp_server': None,
+ 'smtp_port': None,
+ 'smtp_user': None,
+ 'smtp_password': None
+}
+
+
+def load_config(filename, defconf):
+ conf = yaml.safe_load(open(filename, 'r'))
+ actual_config = dict(defconf)
+ actual_config.update(conf)
+ return actual_config
+
+
+def check_config(config, musthave):
+ for key in config:
+ if key not in musthave:
+ raise Exception('Config %s is not known' % key)
+ for key in musthave:
+ if config.get(key) is None:
+ raise Exception('Config %s must not be None' % key)
+
+
+def main():
+ logger = logging.getLogger()
+ logger.setLevel(logging.DEBUG)
+ handler = logging.StreamHandler(sys.stderr)
+ logger.addHandler(handler)
+ logging.info('Starting notification service')
+
+ try:
+
+ name = 'NOTIFICATION_SERVICE_CONFIG'
+ config_filename = os.environ.get(name)
+ logging.info('config filename %r', config_filename)
+ if not config_filename:
+ msg = 'No %s defined in environment' % name
+ logging.error(msg)
+ raise Exception(msg)
+
+ logging.info('reading config from %r', config_filename)
+ config = load_config(config_filename, default_config)
+
+ logging.info('config is %r', config)
+ ick2.setup_logging(config)
+ check_config(config, default_config)
+
+ ick2.log.log(
+ 'info', msg_text='Notification service starts', config=config)
+
+ api = ick2.NotificationAPI(config)
+ ick2.log.log('info', msg_text='created NotificationAPI')
+
+ application = apifw.create_bottle_application(
+ api, counter, dict_logger, config)
+ ick2.log.log('info', msg_text='called apifw.create_bottle_application')
+
+ return application
+ except SystemExit:
+ raise
+ except BaseException as e:
+ logging.error(str(e))
+ ick2.log.log(
+ 'error', msg_text='Uncaught exception', exception=str(e),
+ exc_info=True)
+ sys.exit(1)
+
+
+app = main()
+
+# If we are running this program directly with Python, and not via
+# gunicorn, we can use the Bottle built-in debug server, which can
+# make some things easier to debug.
+
+if __name__ == '__main__':
+ print('running in debug mode')
+ app.run(host='127.0.0.1', port=12767)
diff --git a/notification_service.service b/notification_service.service
new file mode 100644
index 0000000..16f8488
--- /dev/null
+++ b/notification_service.service
@@ -0,0 +1,29 @@
+# Copyright 2018 Lars Wirzenius
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+[Unit]
+Description=ick notification service
+After=network.target
+ConditionPathExists=/etc/ick/notifications.yaml
+
+[Service]
+Type=simple
+User=_ickns
+Group=_ickns
+WorkingDirectory=/var/lib/ick
+ExecStart=/usr/bin/start_notification_service
+KillSignal=QUIT
+
+[Install]
+WantedBy=multi-user.target
diff --git a/pylint.conf b/pylint.conf
index 8a3d3a8..8041750 100644
--- a/pylint.conf
+++ b/pylint.conf
@@ -12,6 +12,7 @@ disable=
redefined-outer-name,
too-few-public-methods,
too-many-arguments,
+ too-many-instance-attributes,
too-many-locals,
too-many-public-methods,
unused-argument,
diff --git a/send-notify-email b/send-notify-email
new file mode 100755
index 0000000..4e134e1
--- /dev/null
+++ b/send-notify-email
@@ -0,0 +1,41 @@
+#!/usr/bin/python3
+# Copyright (C) 2018 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This is a helper program to manually test the code to send
+# notification emails. Run with four args:
+#
+# $0 config recipients build log
+#
+# where all but the log are JSON files. See ick2/sendmail.py for
+# details.
+
+
+import json
+import sys
+
+
+import ick2
+
+
+config = json.load(open(sys.argv[1]))
+recipients = json.load(open(sys.argv[2]))
+build = json.load(open(sys.argv[3]))
+log = open(sys.argv[4]).read()
+
+
+sendmail = ick2.Sendmail()
+sendmail.set_config(config)
+sendmail.send(recipients, build, log)
diff --git a/setup.py b/setup.py
index 70b684a..39a7977 100644
--- a/setup.py
+++ b/setup.py
@@ -49,12 +49,14 @@ setup(
py_modules=[
'ick_controller',
'artifact_store',
+ 'notification_service',
],
packages=['ick2'],
scripts=[
'create-token',
'start_ick',
'start_artifact_store',
+ 'start_notification_service',
'icktool',
'worker_manager'
],
diff --git a/start_notification_service b/start_notification_service
new file mode 100644
index 0000000..c35ef60
--- /dev/null
+++ b/start_notification_service
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Copyright (C) 2018 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set -eux
+
+export NOTIFICATION_SERVICE_CONFIG=/etc/ick/notifications.yaml
+python3 /usr/lib/python3/dist-packages/notification_service.py
diff --git a/without-tests b/without-tests
index 1014317..0e24420 100644
--- a/without-tests
+++ b/without-tests
@@ -7,8 +7,10 @@ ick2/controllerapi.py
ick2/exceptions.py
ick2/logapi.py
ick2/logging.py
+ick2/notificationapi.py
ick2/pipelineapi.py
ick2/responses.py
ick2/trans.py
+ick2/sendmail.py
ick2/version.py
ick2/workerapi.py
diff --git a/worker_manager b/worker_manager
index d0ee145..ab267d5 100755
--- a/worker_manager
+++ b/worker_manager
@@ -129,11 +129,14 @@ class ControllerAPI:
_scopes = ' '.join([
'uapi_version_get',
+ 'uapi_notify_post',
'uapi_work_get',
'uapi_work_post',
'uapi_workers_post',
'uapi_blobs_id_get',
'uapi_blobs_id_put',
+ 'uapi_builds_id_get',
+ 'uapi_logs_id_get',
])
def __init__(self, name, url):
@@ -145,6 +148,9 @@ class ControllerAPI:
self._client_id = None
self._client_secret = None
+ def get_controller_client(self):
+ return self._cc
+
def set_verify_tls(self, verify):
self._cc.set_verify_tls(verify)
@@ -199,10 +205,13 @@ class Worker:
def do_work(self, work):
project_name = work['project']
build_number = work['build_number']
+ build_id = work['build_id']
step = work.get('step', {})
params = work.get('parameters', {})
reporter = ick2.Reporter(self._api, work)
- af = ick2.ActionFactory(self._systree, self._workspace, reporter)
+ af = ick2.ActionFactory(
+ build_id, self._systree, self._workspace, reporter)
+ af.set_controller_client(self._api.get_controller_client())
af.set_token(self._api.get_token())
af.set_blob_url_func(self._api.get_blob_upload_url)
af.add_env_var('LC_ALL', 'C')
diff --git a/yarns/100-projects.yarn b/yarns/100-projects.yarn
index c5052f9..2fddded 100644
--- a/yarns/100-projects.yarn
+++ b/yarns/100-projects.yarn
@@ -57,6 +57,7 @@ building them. We start by starting an instance of the controller.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND a running ick controller
WHEN user makes request GET /projects
diff --git a/yarns/150-pipelines.yarn b/yarns/150-pipelines.yarn
index a63ce02..e2416a8 100644
--- a/yarns/150-pipelines.yarn
+++ b/yarns/150-pipelines.yarn
@@ -68,6 +68,7 @@ running them. We start by starting an instance of the controller.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND a running ick controller
WHEN user makes request GET /pipelines
diff --git a/yarns/200-version.yarn b/yarns/200-version.yarn
index 8d8078d..710a57a 100644
--- a/yarns/200-version.yarn
+++ b/yarns/200-version.yarn
@@ -28,6 +28,7 @@ The Ick controller reports is version upon request.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND a running ick controller
WHEN user makes request GET /version
@@ -35,6 +36,7 @@ The Ick controller reports is version upon request.
AND version in body matches version from setup.py
AND artifact store URL is https://blobs.example.com
AND authentication URL is https://auth.example.com
+ AND notify URL is https://notify.example.com
FINALLY stop ick controller
diff --git a/yarns/300-workers.yarn b/yarns/300-workers.yarn
index 89444f9..6399b20 100644
--- a/yarns/300-workers.yarn
+++ b/yarns/300-workers.yarn
@@ -63,6 +63,7 @@ controller API. It doesn't actually talk to the worker itself.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND a running ick controller
WHEN user makes request GET /workers
diff --git a/yarns/400-build.yarn b/yarns/400-build.yarn
index 1674189..cef92e9 100644
--- a/yarns/400-build.yarn
+++ b/yarns/400-build.yarn
@@ -29,6 +29,7 @@ Set up the controller.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND an access token for user with scopes
... uapi_pipelines_post
... uapi_projects_post
@@ -820,6 +821,7 @@ Set up the controller.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND an access token for user with scopes
... uapi_pipelines_post
... uapi_projects_post
@@ -1027,6 +1029,7 @@ Set up the controller.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND an access token for user with scopes
... uapi_pipelines_post
... uapi_projects_post
diff --git a/yarns/500-build-fail.yarn b/yarns/500-build-fail.yarn
index 204bfd5..3373c2f 100644
--- a/yarns/500-build-fail.yarn
+++ b/yarns/500-build-fail.yarn
@@ -30,6 +30,7 @@ Set up the controller.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND an access token for user with scopes
... uapi_pipelines_post
... uapi_projects_post
diff --git a/yarns/600-unauthz.yarn b/yarns/600-unauthz.yarn
index c656f0e..1c928ac 100644
--- a/yarns/600-unauthz.yarn
+++ b/yarns/600-unauthz.yarn
@@ -30,6 +30,7 @@ Set up the controller.
AND controller config uses statedir at the state directory
AND controller config uses https://blobs.example.com as artifact store
AND controller config uses https://auth.example.com as authentication
+ AND controller config uses https://notify.example.com as notify
AND an access token for user with scopes
... uapi_projects_post
... uapi_projects_id_status_put
diff --git a/yarns/900-local.yarn b/yarns/900-local.yarn
index 40bf762..1318eb9 100644
--- a/yarns/900-local.yarn
+++ b/yarns/900-local.yarn
@@ -53,6 +53,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
IMPLEMENTS GIVEN controller config uses (\S+) as authentication
vars['auth_url'] = get_next_match()
+ IMPLEMENTS GIVEN controller config uses (\S+) as notify
+ vars['notify_url'] = get_next_match()
+ assert vars['notify_url'] is not None
+
## Start and stop the controller
IMPLEMENTS GIVEN a running ick controller
@@ -62,6 +66,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
vars['port'] = random_free_port()
vars['url'] = 'http://127.0.0.1:{}'.format(vars['port'])
assert vars['auth_url'] is not None
+ assert vars['notify_url'] is not None
config = {
'token-issuer': vars['issuer'],
'token-audience': vars['audience'],
@@ -74,7 +79,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
'statedir': vars['statedir'],
'artifact-store': vars['artifact_store'],
'auth-url': vars['auth_url'],
+ 'notify-url': vars['notify_url'],
}
+ assert config['notify-url'] is not None
env = dict(os.environ)
env['ICK_CONTROLLER_CONFIG'] = 'ick_controller.yaml'
yaml.safe_dump(config, open('ick_controller.yaml', 'w'))
@@ -131,6 +138,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
actual = obj['auth_url']
assertEqual(actual, expected)
+ IMPLEMENTS THEN notify URL is (\S+)
+ expected = get_next_match()
+ body = vars['body']
+ obj = json.loads(body)
+ actual = obj['notify_url']
+ assertEqual(actual, expected)
+
## Start and stop artifact store
IMPLEMENTS GIVEN artifact store config uses (\S+) at the blob directory