summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ick2/__init__.py2
-rw-r--r--ick2/controllerapi.py205
-rw-r--r--ick2/workapi.py219
-rw-r--r--without-tests1
4 files changed, 222 insertions, 205 deletions
diff --git a/ick2/__init__.py b/ick2/__init__.py
index 6c19bed..2b4bae8 100644
--- a/ick2/__init__.py
+++ b/ick2/__init__.py
@@ -32,9 +32,9 @@ from .apibase import APIbase, ResourceApiBase
from .buildsapi import BuildsAPI
from .logapi import LogAPI
from .versionapi import VersionAPI
+from .workapi import WorkAPI
from .workerapi import WorkerAPI
from .controllerapi import (
ControllerAPI,
ProjectAPI,
- WorkAPI,
)
diff --git a/ick2/controllerapi.py b/ick2/controllerapi.py
index 5d5e86d..4283e82 100644
--- a/ick2/controllerapi.py
+++ b/ick2/controllerapi.py
@@ -33,7 +33,7 @@ class ControllerAPI:
'/builds': ick2.BuildsAPI,
'/logs': ick2.LogAPI,
'/projects': ProjectAPI,
- '/work': WorkAPI,
+ '/work': ick2.WorkAPI,
'/workers': ick2.WorkerAPI,
}
@@ -114,206 +114,3 @@ class ProjectAPI(ick2.ResourceApiBase):
'project': project,
'builds': p.get('builds', []),
}
-
-
-class WorkAPI(ick2.APIbase):
-
- def __init__(self, state):
- super().__init__(state)
- self._type_name = 'work'
-
- def get_routes(self, path): # pragma: no cover
- return [
- {
- 'method': 'GET',
- 'path': '{}/<worker>'.format(path),
- 'callback': self.GET(self.get_work),
- },
- {
- 'method': 'POST',
- 'path': path,
- 'callback': self.POST(self.update_work),
- },
- ]
-
- def get_work(self, worker):
- worker_state = self._get_worker(worker)
- if not worker_state.get('doing'):
- project, pipeline = self._pick_triggered_pipeline()
- if project is None:
- doing = {}
- else:
- pipeline['status'] = 'building'
- self._update_project(project)
-
- build_id = self._start_build(project, pipeline, worker)
- self._start_log(build_id)
-
- index = 0
- doing = {
- 'build_id': build_id,
- 'worker': worker,
- 'project': project['project'],
- 'pipeline': pipeline['name'],
- 'step': pipeline['actions'][index],
- 'step_index': index,
- 'log': '/logs/{}'.format(build_id),
- }
-
- worker_state = {
- 'worker': worker,
- 'doing': doing,
- }
- self._update_worker(worker_state)
-
- return worker_state['doing']
-
- def _get_worker(self, worker): # pragma: no cover
- try:
- return self._state.get_resource('workers', worker)
- except ick2.NotFound:
- return {
- 'worker': worker,
- }
-
- def _update_worker(self, worker_state):
- self._state.update_resource(
- 'workers', worker_state['worker'], worker_state)
-
- def _pick_triggered_pipeline(self):
- projects = self._get_projects()
- for project in projects:
- for pipeline in project['pipelines']:
- if pipeline.get('status') == 'triggered':
- return project, pipeline
- return None, None
-
- def _get_projects(self):
- return self._state.get_resources('projects')
-
- def _update_project(self, project):
- self._state.update_resource('projects', project['project'], project)
-
- def update_work(self, update):
- if 'worker' not in update: # pragma: no cover
- raise ick2.BadUpdate('no worker specified')
-
- worker_state = self._get_worker(update['worker'])
- doing = worker_state.get('doing', {})
- self._check_work_update(doing, update)
-
- project, pipeline = self._get_pipeline(
- update['project'], update['pipeline'])
- self._append_to_build_log(update)
-
- ick2.log.log(
- 'trace',
- msg_text='xxx update_work',
- update=update,
- project=project,
- pipeline=pipeline,
- doing=doing)
-
- exit_code = update.get('exit_code')
- if exit_code == 0:
- index = doing['step_index'] + 1
- actions = pipeline['actions']
- if index >= len(actions):
- pipeline['status'] = 'idle'
- doing = {}
- self._finish_build(update)
- else:
- doing['step_index'] = index
- doing['step'] = actions[index]
- self._update_project(project)
-
- worker_state = {
- 'worker': update['worker'],
- 'doing': doing,
- }
- self._update_worker(worker_state)
- elif exit_code is not None:
- assert isinstance(exit_code, int)
- assert exit_code != 0
- pipeline['status'] = 'idle'
- self._update_project(project)
- self._finish_build(update)
-
- worker_state = {
- 'worker': update['worker'],
- 'doing': {},
- }
- self._update_worker(worker_state)
-
- def _check_work_update(self, doing, update): # pragma: no cover
- must_match = ['worker', 'project', 'pipeline', 'build_id']
- for name in must_match:
- if name not in update:
- raise ick2.BadUpdate('{} not specified'.format(name))
- if doing.get(name) != update[name]:
- raise ick2.BadUpdate(
- '{} differs from current work: {} vs {}'.format(
- name, doing.get(name), update[name]))
-
- def _get_pipeline(self, project, pipeline): # pragma: no cover
- projects = self._get_projects()
- for p in projects:
- for pl in p['pipelines']:
- if pl.get('name') == pipeline:
- return p, pl
- raise ick2.NotFound()
-
- def _start_build(self, project, pipeline, worker):
- ick2.log.log('info', msg_text='Starting new build')
- build_id = project.get('build_id', 0)
- build_id += 1
- project['build_id'] = build_id
- self._update_project(project)
- build = {
- 'build_id': build_id,
- 'log': '/logs/{}'.format(build_id),
- 'worker': worker,
- 'project': project['project'],
- 'pipeline': pipeline['name'],
- 'status': 'building',
- }
- self._state.add_resource('builds', str(build_id), build)
- return build_id
-
- def _start_log(self, build_id):
- ick2.log.log('info', msg_text='Starting new log', build_id=build_id)
- log = {
- 'build_id': build_id,
- 'log': '',
- }
- self._state.add_resource('log', str(build_id), log)
- return build_id
-
- def _append_to_build_log(self, update):
- build_id = update['build_id']
- log = self._state.get_resource('log', str(build_id))
- for kind in ['stdout', 'stderr']:
- text = update.get(kind, '')
- if text is not None:
- log['log'] += text
- self._state.update_resource('log', str(build_id), log)
-
- def _finish_build(self, update):
- build = self._state.get_resource('builds', str(update['build_id']))
- build['status'] = update['exit_code']
- self._state.update_resource('builds', str(update['build_id']), build)
-
- def create(self, *args, **kwargs): # pragma: no cover
- pass
-
- def update(self, *args, **kwargs): # pragma: no cover
- pass
-
- def list(self, *args, **kwargs): # pragma: no cover
- pass
-
- def show(self, *args, **kwargs): # pragma: no cover
- pass
-
- def delete(self, *args, **kwargs): # pragma: no cover
- pass
diff --git a/ick2/workapi.py b/ick2/workapi.py
new file mode 100644
index 0000000..3f0b195
--- /dev/null
+++ b/ick2/workapi.py
@@ -0,0 +1,219 @@
+# Copyright (C) 2017 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 WorkAPI(ick2.APIbase):
+
+ def __init__(self, state):
+ super().__init__(state)
+ self._type_name = 'work'
+
+ def get_routes(self, path): # pragma: no cover
+ return [
+ {
+ 'method': 'GET',
+ 'path': '{}/<worker>'.format(path),
+ 'callback': self.GET(self.get_work),
+ },
+ {
+ 'method': 'POST',
+ 'path': path,
+ 'callback': self.POST(self.update_work),
+ },
+ ]
+
+ def get_work(self, worker):
+ worker_state = self._get_worker(worker)
+ if not worker_state.get('doing'):
+ project, pipeline = self._pick_triggered_pipeline()
+ if project is None:
+ doing = {}
+ else:
+ pipeline['status'] = 'building'
+ self._update_project(project)
+
+ build_id = self._start_build(project, pipeline, worker)
+ self._start_log(build_id)
+
+ index = 0
+ doing = {
+ 'build_id': build_id,
+ 'worker': worker,
+ 'project': project['project'],
+ 'pipeline': pipeline['name'],
+ 'step': pipeline['actions'][index],
+ 'step_index': index,
+ 'log': '/logs/{}'.format(build_id),
+ }
+
+ worker_state = {
+ 'worker': worker,
+ 'doing': doing,
+ }
+ self._update_worker(worker_state)
+
+ return worker_state['doing']
+
+ def _get_worker(self, worker): # pragma: no cover
+ try:
+ return self._state.get_resource('workers', worker)
+ except ick2.NotFound:
+ return {
+ 'worker': worker,
+ }
+
+ def _update_worker(self, worker_state):
+ self._state.update_resource(
+ 'workers', worker_state['worker'], worker_state)
+
+ def _pick_triggered_pipeline(self):
+ projects = self._get_projects()
+ for project in projects:
+ for pipeline in project['pipelines']:
+ if pipeline.get('status') == 'triggered':
+ return project, pipeline
+ return None, None
+
+ def _get_projects(self):
+ return self._state.get_resources('projects')
+
+ def _update_project(self, project):
+ self._state.update_resource('projects', project['project'], project)
+
+ def update_work(self, update):
+ if 'worker' not in update: # pragma: no cover
+ raise ick2.BadUpdate('no worker specified')
+
+ worker_state = self._get_worker(update['worker'])
+ doing = worker_state.get('doing', {})
+ self._check_work_update(doing, update)
+
+ project, pipeline = self._get_pipeline(
+ update['project'], update['pipeline'])
+ self._append_to_build_log(update)
+
+ ick2.log.log(
+ 'trace',
+ msg_text='xxx update_work',
+ update=update,
+ project=project,
+ pipeline=pipeline,
+ doing=doing)
+
+ exit_code = update.get('exit_code')
+ if exit_code == 0:
+ index = doing['step_index'] + 1
+ actions = pipeline['actions']
+ if index >= len(actions):
+ pipeline['status'] = 'idle'
+ doing = {}
+ self._finish_build(update)
+ else:
+ doing['step_index'] = index
+ doing['step'] = actions[index]
+ self._update_project(project)
+
+ worker_state = {
+ 'worker': update['worker'],
+ 'doing': doing,
+ }
+ self._update_worker(worker_state)
+ elif exit_code is not None:
+ assert isinstance(exit_code, int)
+ assert exit_code != 0
+ pipeline['status'] = 'idle'
+ self._update_project(project)
+ self._finish_build(update)
+
+ worker_state = {
+ 'worker': update['worker'],
+ 'doing': {},
+ }
+ self._update_worker(worker_state)
+
+ def _check_work_update(self, doing, update): # pragma: no cover
+ must_match = ['worker', 'project', 'pipeline', 'build_id']
+ for name in must_match:
+ if name not in update:
+ raise ick2.BadUpdate('{} not specified'.format(name))
+ if doing.get(name) != update[name]:
+ raise ick2.BadUpdate(
+ '{} differs from current work: {} vs {}'.format(
+ name, doing.get(name), update[name]))
+
+ def _get_pipeline(self, project, pipeline): # pragma: no cover
+ projects = self._get_projects()
+ for p in projects:
+ for pl in p['pipelines']:
+ if pl.get('name') == pipeline:
+ return p, pl
+ raise ick2.NotFound()
+
+ def _start_build(self, project, pipeline, worker):
+ ick2.log.log('info', msg_text='Starting new build')
+ build_id = project.get('build_id', 0)
+ build_id += 1
+ project['build_id'] = build_id
+ self._update_project(project)
+ build = {
+ 'build_id': build_id,
+ 'log': '/logs/{}'.format(build_id),
+ 'worker': worker,
+ 'project': project['project'],
+ 'pipeline': pipeline['name'],
+ 'status': 'building',
+ }
+ self._state.add_resource('builds', str(build_id), build)
+ return build_id
+
+ def _start_log(self, build_id):
+ ick2.log.log('info', msg_text='Starting new log', build_id=build_id)
+ log = {
+ 'build_id': build_id,
+ 'log': '',
+ }
+ self._state.add_resource('log', str(build_id), log)
+ return build_id
+
+ def _append_to_build_log(self, update):
+ build_id = update['build_id']
+ log = self._state.get_resource('log', str(build_id))
+ for kind in ['stdout', 'stderr']:
+ text = update.get(kind, '')
+ if text is not None:
+ log['log'] += text
+ self._state.update_resource('log', str(build_id), log)
+
+ def _finish_build(self, update):
+ build = self._state.get_resource('builds', str(update['build_id']))
+ build['status'] = update['exit_code']
+ self._state.update_resource('builds', str(update['build_id']), build)
+
+ def create(self, *args, **kwargs): # pragma: no cover
+ pass
+
+ def update(self, *args, **kwargs): # pragma: no cover
+ pass
+
+ def list(self, *args, **kwargs): # pragma: no cover
+ pass
+
+ def show(self, *args, **kwargs): # pragma: no cover
+ pass
+
+ def delete(self, *args, **kwargs): # pragma: no cover
+ pass
diff --git a/without-tests b/without-tests
index 51270a1..760d380 100644
--- a/without-tests
+++ b/without-tests
@@ -7,4 +7,5 @@ ick2/logging.py
ick2/responses.py
ick2/version.py
ick2/versionapi.py
+ick2/workapi.py
ick2/workerapi.py