summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2017-11-26 18:03:15 +0100
committerLars Wirzenius <liw@liw.fi>2017-11-26 18:03:15 +0100
commit7b06ae78e20717161467affb37b88857e4fc610b (patch)
treed33dbbb46479c5372b56386c88698e80e1c79823
parentdb3e88505f0a6ad11519cf1615444b1d423d42fe (diff)
parent7a2829fe5457c05d1bd25fbba9bc7d16eb091b59 (diff)
downloadick2-7b06ae78e20717161467affb37b88857e4fc610b.tar.gz
Merge branch 'liw/namedpipes'
-rw-r--r--NEWS4
-rw-r--r--ick2/__init__.py1
-rw-r--r--ick2/apibase.py10
-rw-r--r--ick2/controllerapi.py1
-rw-r--r--ick2/pipelineapi.py25
-rw-r--r--ick2/projectapi.py58
-rw-r--r--ick2/projectapi_tests.py56
-rw-r--r--ick2/workapi.py37
-rw-r--r--ick2/workapi_tests.py25
-rw-r--r--without-tests1
-rw-r--r--yarns/100-projects.yarn105
-rw-r--r--yarns/150-pipelines.yarn168
-rw-r--r--yarns/400-build.yarn31
-rw-r--r--yarns/500-build-fail.yarn22
14 files changed, 371 insertions, 173 deletions
diff --git a/NEWS b/NEWS
index bdf6d12..7bc957e 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,10 @@ Version 0.18+git, not yet released
work steps that the worker-manager gets, include the parameters.
Worker-manager does not do anything with them yet.
+* Pipelines are now shared between projects, and projects refer to
+ them by name. Pipeline status is per project, however, so if two
+ project refer to the same pipeline, they can trigger it separately.
+
Version 0.18, released 2017-11-25
----------------------------------
diff --git a/ick2/__init__.py b/ick2/__init__.py
index 13912fe..9b4d9e3 100644
--- a/ick2/__init__.py
+++ b/ick2/__init__.py
@@ -34,6 +34,7 @@ from .apibase import APIbase, ResourceApiBase
from .buildsapi import BuildsAPI
from .logapi import LogAPI
from .versionapi import VersionAPI
+from .pipelineapi import PipelineAPI
from .projectapi import ProjectAPI
from .workapi import WorkAPI
from .workerapi import WorkerAPI
diff --git a/ick2/apibase.py b/ick2/apibase.py
index 361baaa..1e4fd14 100644
--- a/ick2/apibase.py
+++ b/ick2/apibase.py
@@ -58,8 +58,10 @@ class APIbase:
try:
if 'raw_uri_path' in kwargs:
del kwargs['raw_uri_path']
- body = callback(**kwargs)
+ body = callback(**kwargs)
except ick2.NotFound as e:
+ ick2.log.log(
+ 'warning', msg_text='GET Not found', kwargs=kwargs)
return ick2.not_found(e)
if isinstance(body, dict):
return ick2.OK(body)
@@ -76,7 +78,7 @@ class APIbase:
try:
body = callback(body)
except ick2.ExistsAlready as e:
- ick2.log.log('error', msg_text=str(e))
+ ick2.log.log('error', msg_text=str(e), kwargs=kwargs)
return ick2.conflict(str(e))
return ick2.created(body)
return wrapper
@@ -91,6 +93,8 @@ class APIbase:
try:
body = callback(body, **kwargs)
except ick2.NotFound as e:
+ ick2.log.log(
+ 'warning', msg_text='PUT Not found', kwargs=kwargs)
return ick2.not_found(e)
except ick2.WrongPipelineStatus as e:
ick2.log.log(
@@ -112,6 +116,8 @@ class APIbase:
del kwargs['raw_uri_path']
body = callback(**kwargs)
except ick2.NotFound as e:
+ ick2.log.log(
+ 'warning', msg_text='DELETE Not found', kwargs=kwargs)
return ick2.not_found(e)
return ick2.OK(body)
return wrapper
diff --git a/ick2/controllerapi.py b/ick2/controllerapi.py
index 4b67d10..1ef71cc 100644
--- a/ick2/controllerapi.py
+++ b/ick2/controllerapi.py
@@ -32,6 +32,7 @@ class ControllerAPI:
'/version': ick2.VersionAPI,
'/builds': ick2.BuildsAPI,
'/logs': ick2.LogAPI,
+ '/pipelines': ick2.PipelineAPI,
'/projects': ick2.ProjectAPI,
'/work': ick2.WorkAPI,
'/workers': ick2.WorkerAPI,
diff --git a/ick2/pipelineapi.py b/ick2/pipelineapi.py
new file mode 100644
index 0000000..061505e
--- /dev/null
+++ b/ick2/pipelineapi.py
@@ -0,0 +1,25 @@
+# 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 PipelineAPI(ick2.ResourceApiBase):
+
+ def __init__(self, state):
+ super().__init__('pipelines', state)
+
+ def get_resource_name(self, resource):
+ return resource['name']
diff --git a/ick2/projectapi.py b/ick2/projectapi.py
index b0c9da1..f46fe14 100644
--- a/ick2/projectapi.py
+++ b/ick2/projectapi.py
@@ -50,33 +50,59 @@ class ProjectAPI(ick2.ResourceApiBase):
def get_pipeline(self, project, pipeline, **kwargs):
p = self._state.get_resource(self._type_name, project)
- for pl in p['pipelines']:
- if pl['name'] == pipeline:
- return {
- 'status': pl.get('status', 'idle'),
- }
- raise ick2.NotFound()
+ if pipeline not in p['pipelines']:
+ raise ick2.NotFound()
+
+ pp = self._pipeline_instance_name(project, pipeline)
+ try:
+ pl = self._state.get_resource('pipeline_instances', pp)
+ except ick2.NotFound:
+ pl = {}
+ return {
+ 'status': pl.get('status', 'idle'),
+ }
+
+ def _pipeline_instance_name(self, project_name, pipeline_name):
+ return '{} {}'.format(project_name, pipeline_name)
def set_pipeline_callback(
self, body, project, pipeline, **kwargs): # pragma: no cover
return self.set_pipeline(body['status'], project, pipeline)
- def set_pipeline(self, state, project, pipeline):
+ def set_pipeline(self, status, project, pipeline):
+ ick2.log.log(
+ 'trace', msg_text='Setting pipeline status',
+ project=project, pipeline=pipeline, status=status)
+
allowed_changes = {
'idle': 'triggered',
'triggered': 'building',
'building': 'idle',
}
+
p = self._state.get_resource(self._type_name, project)
- for pl in p['pipelines']:
- if pl['name'] == pipeline:
- old_state = pl.get('status', 'idle')
- if allowed_changes[old_state] != state:
- raise ick2.WrongPipelineStatus(state)
- pl['status'] = state
- self._state.update_resource(self._type_name, project, p)
- return {'status': state}
- raise ick2.NotFound()
+ if pipeline not in p['pipelines']:
+ ick2.log.log(
+ 'error', msg_text='Project not found', project=project)
+ raise ick2.NotFound()
+ ick2.log.log('trace', msg_text='Found project', project=p)
+
+ pp = self._pipeline_instance_name(project, pipeline)
+ try:
+ pl = self._state.get_resource('pipeline_instances', pp)
+ except ick2.NotFound:
+ pl = {
+ 'name': pipeline,
+ 'status': 'idle',
+ }
+ self._state.add_resource('pipeline_instances', pp, pl)
+
+ old_status = pl.get('status', 'idle')
+ if allowed_changes[old_status] != status:
+ raise ick2.WrongPipelineStatus(status)
+ pl['status'] = status
+ self._state.update_resource('pipeline_instances', pp, pl)
+ return {'status': status}
# This needs to go away as it is not protected. Once an IDP is
# added.
diff --git a/ick2/projectapi_tests.py b/ick2/projectapi_tests.py
index ae5190e..4e55ccf 100644
--- a/ick2/projectapi_tests.py
+++ b/ick2/projectapi_tests.py
@@ -36,28 +36,32 @@ class ProjectAPITests(unittest.TestCase):
def create_api(self):
return ick2.ProjectAPI(self.state)
+ def create_pipeline_api(self):
+ return ick2.PipelineAPI(self.state)
+
def test_has_not_projects_initially(self):
api = self.create_api()
self.assertEqual(api.list(), {'projects': []})
def test_creates_project(self):
+ pipeline = {
+ 'name': 'build',
+ 'actions': [
+ {'shell': 'step-1'},
+ ],
+ }
+ pipeapi = self.create_pipeline_api()
+ pipeapi.create(pipeline)
+
project = {
'project': 'foo',
- 'pipelines': [
- {
- 'name': 'build',
- 'actions': [
- {
- 'shell': 'step-1',
- },
- ],
- },
- ],
+ 'pipelines': ['build'],
'parameters': {
'foo': 'bar',
}
}
api = self.create_api()
+
self.assertEqual(api.create(project), project)
self.assertEqual(api.list(), {'projects': [project]})
self.assertEqual(api.get_pipeline('foo', 'build'), {'status': 'idle'})
@@ -85,7 +89,7 @@ class ProjectAPITests(unittest.TestCase):
def test_loads_projects_from_state_directory(self):
project = {
'project': 'foo',
- 'shell_steps': ['build'],
+ 'pipelines': ['build'],
}
api = self.create_api()
api.create(project)
@@ -96,7 +100,7 @@ class ProjectAPITests(unittest.TestCase):
def test_gets_named_project(self):
project = {
'project': 'foo',
- 'shell_steps': ['build'],
+ 'pipelines': ['build'],
}
api = self.create_api()
api.create(project)
@@ -105,7 +109,7 @@ class ProjectAPITests(unittest.TestCase):
def test_updates_named_project(self):
project_v1 = {
'project': 'foo',
- 'shell_steps': ['build'],
+ 'pipelines': ['build'],
}
project_v2 = dict(project_v1)
project_v2['shell_steps'] = ['build it using magic']
@@ -118,7 +122,7 @@ class ProjectAPITests(unittest.TestCase):
def test_deletes_named_project(self):
project = {
'project': 'foo',
- 'shell_steps': ['build'],
+ 'pipelines': ['build'],
}
api = self.create_api()
api.create(project)
@@ -133,27 +137,29 @@ class ProjectAPITests(unittest.TestCase):
api.delete('foo')
def test_updates_pipeline_status(self):
+ pipeline = {
+ 'name': 'build',
+ 'actions': [
+ {'shell': 'step-1'},
+ ],
+ }
+ pipeapi = self.create_pipeline_api()
+ pipeapi.create(pipeline)
+
project = {
'project': 'foo',
- 'pipelines': [
- {
- 'name': 'build',
- 'actions': [
- {
- 'shell': 'step-1',
- },
- ],
- },
- ],
+ 'pipelines': ['build'],
}
api = self.create_api()
api.create(project)
self.assertEqual(api.get_pipeline('foo', 'build'), {'status': 'idle'})
+ self.assertEqual(pipeapi.show('build'), pipeline)
with self.assertRaises(ick2.WrongPipelineStatus):
api.set_pipeline('building', 'foo', 'build')
api.set_pipeline('triggered', 'foo', 'build')
+ self.assertEqual(pipeapi.show('build'), pipeline)
self.assertEqual(
api.get_pipeline('foo', 'build'),
{'status': 'triggered'}
@@ -163,6 +169,7 @@ class ProjectAPITests(unittest.TestCase):
api.set_pipeline('idle', 'foo', 'build')
api.set_pipeline('building', 'foo', 'build')
+ self.assertEqual(pipeapi.show('build'), pipeline)
self.assertEqual(
api.get_pipeline('foo', 'build'),
{'status': 'building'}
@@ -172,6 +179,7 @@ class ProjectAPITests(unittest.TestCase):
api.set_pipeline('triggered', 'foo', 'build')
api.set_pipeline('idle', 'foo', 'build')
+ self.assertEqual(pipeapi.show('build'), pipeline)
self.assertEqual(
api.get_pipeline('foo', 'build'),
{'status': 'idle'}
diff --git a/ick2/workapi.py b/ick2/workapi.py
index 5935fe9..f84e355 100644
--- a/ick2/workapi.py
+++ b/ick2/workapi.py
@@ -46,7 +46,7 @@ class WorkAPI(ick2.APIbase):
doing = {}
else:
pipeline['status'] = 'building'
- self._projects.update_project(project)
+ self._update_pipeline(project, pipeline)
build_id = project.get('build_id', 0) + 1
project['build_id'] = build_id
@@ -78,11 +78,30 @@ class WorkAPI(ick2.APIbase):
def _pick_triggered_pipeline(self):
projects = self._projects.get_projects()
for project in projects:
- for pipeline in project['pipelines']:
- if pipeline.get('status') == 'triggered':
- return project, pipeline
+ for name in project['pipelines']:
+ pp = self._pipeline_instance_name(project['project'], name)
+ try:
+ pl = self._state.get_resource('pipeline_instances', pp)
+ except ick2.NotFound:
+ pass
+ else:
+ if pl.get('status') == 'triggered':
+ pt = self._state.get_resource('pipelines', name)
+ return project, pt
return None, None
+ def _pipeline_instance_name(self, project_name, pipeline_name):
+ return '{} {}'.format(project_name, pipeline_name)
+
+ def _update_pipeline(self, project, pipeline):
+ pp = self._pipeline_instance_name(project['project'], pipeline['name'])
+ try:
+ self._state.get_resource('pipeline_instances', pp)
+ except ick2.NotFound: # pragma: no cover
+ self._state.add_resource('pipeline_instances', pp, pipeline)
+ else:
+ self._state.update_resource('pipeline_instances', pp, pipeline)
+
def _start_build(self, project, pipeline, worker, build_id):
ick2.log.log('info', msg_text='Starting new build', build_id=build_id)
build = {
@@ -136,6 +155,7 @@ class WorkAPI(ick2.APIbase):
doing['step_index'] = index
doing['step'] = actions[index]
self._projects.update_project(project)
+ self._update_pipeline(project, pipeline)
worker_state = {
'worker': update['worker'],
@@ -146,7 +166,7 @@ class WorkAPI(ick2.APIbase):
assert isinstance(exit_code, int)
assert exit_code != 0
pipeline['status'] = 'idle'
- self._projects.update_project(project)
+ self._update_pipeline(project, pipeline)
self._finish_build(update)
worker_state = {
@@ -168,9 +188,10 @@ class WorkAPI(ick2.APIbase):
def _get_pipeline(self, project, pipeline): # pragma: no cover
projects = self._projects.get_projects()
for p in projects:
- for pl in p['pipelines']:
- if pl.get('name') == pipeline:
- return p, pl
+ for name in p['pipelines']:
+ if name == pipeline:
+ pt = self._state.get_resource('pipelines', name)
+ return p, pt
raise ick2.NotFound()
def _append_to_build_log(self, update):
diff --git a/ick2/workapi_tests.py b/ick2/workapi_tests.py
index f657687..c855895 100644
--- a/ick2/workapi_tests.py
+++ b/ick2/workapi_tests.py
@@ -34,24 +34,23 @@ class WorkAPITests(unittest.TestCase):
shutil.rmtree(self.tempdir)
def create_project_api(self):
+ pipeline = {
+ 'name': 'build',
+ 'actions': [
+ {'shell': 'step-1'},
+ {'shell': 'step-2'},
+ ],
+ }
+
+ pipeapi = ick2.PipelineAPI(self.state)
+ pipeapi.create(pipeline)
+
project = {
'project': 'foo',
'parameters': {
'foo': 'bar',
},
- 'pipelines': [
- {
- 'name': 'build',
- 'actions': [
- {
- 'shell': 'step-1',
- },
- {
- 'shell': 'step-2',
- },
- ],
- },
- ],
+ 'pipelines': ['build'],
}
api = ick2.ProjectAPI(self.state)
api.create(project)
diff --git a/without-tests b/without-tests
index b587c70..6b58af3 100644
--- a/without-tests
+++ b/without-tests
@@ -4,6 +4,7 @@ ick2/buildsapi.py
ick2/exceptions.py
ick2/logapi.py
ick2/logging.py
+ick2/pipelineapi.py
ick2/responses.py
ick2/version.py
ick2/workerapi.py
diff --git a/yarns/100-projects.yarn b/yarns/100-projects.yarn
index aa04b7a..ba723d3 100644
--- a/yarns/100-projects.yarn
+++ b/yarns/100-projects.yarn
@@ -27,24 +27,9 @@ like this:
{
"project": "ick2-website",
"pipelines": [
- {
- "name": "setup-workspace",
- "actions": [
- { "shell": "git clone git://git.liw.fi/ick2-website src" }
- ]
- },
- {
- "name": "build-website",
- "actions": [
- { "shell": "cd src && ikiwiki --setup ikiwiki.setup" }
- ]
- },
- {
- "name": "publish-website",
- "actions": [
- { "shell": "cd html && rsync -a --delete . server::/srv/http/ick2/." }
- ]
- }
+ "setup-workspace",
+ "build-website",
+ "publish-website"
]
}
@@ -63,6 +48,7 @@ building them. We start by starting an instance of the controller.
SCENARIO managing projects
GIVEN an RSA key pair for token signing
AND an access token for user with scopes
+ ... uapi_pipelines_post
... uapi_projects_get
... uapi_projects_post
... uapi_projects_id_get
@@ -75,34 +61,25 @@ building them. We start by starting an instance of the controller.
THEN result has status code 200
AND body matches { "projects": [] }
+ WHEN user makes request POST /pipelines with a valid token and body
+ ... {
+ ... "name": "build",
+ ... "actions": [
+ ... { "shell": "git clone git://repo src" },
+ ... { "shell": "mkdir html" },
+ ... { "shell": "ikiwiki src html" }
+ ... ]
+ ... }
WHEN user makes request POST /projects with a valid token and body
... {
... "project": "website",
- ... "pipelines": [
- ... {
- ... "name": "build",
- ... "actions": [
- ... { "shell": "git clone git://repo src" },
- ... { "shell": "mkdir html" },
- ... { "shell": "ikiwiki src html" }
- ... ]
- ... }
- ... ]
+ ... "pipelines": ["build"]
... }
THEN result has status code 201
AND body matches
... {
... "project": "website",
- ... "pipelines": [
- ... {
- ... "name": "build",
- ... "actions": [
- ... { "shell": "git clone git://repo src" },
- ... { "shell": "mkdir html" },
- ... { "shell": "ikiwiki src html" }
- ... ]
- ... }
- ... ]
+ ... "pipelines": ["build"]
... }
AND controller state directory contains project website
@@ -122,16 +99,7 @@ Creating a new project with the same name is forbidden.
... "projects": [
... {
... "project": "website",
- ... "pipelines": [
- ... {
- ... "name": "build",
- ... "actions": [
- ... { "shell": "git clone git://repo src" },
- ... { "shell": "mkdir html" },
- ... { "shell": "ikiwiki src html" }
- ... ]
- ... }
- ... ]
+ ... "pipelines": ["build"]
... }
... ]
... }
@@ -143,43 +111,22 @@ Creating a new project with the same name is forbidden.
AND body matches
... {
... "project": "website",
- ... "pipelines": [
- ... {
- ... "name": "build",
- ... "actions": [
- ... { "shell": "git clone git://repo src" },
- ... { "shell": "mkdir html" },
- ... { "shell": "ikiwiki src html" }
- ... ]
- ... }
- ... ]
+ ... "pipelines": ["build"]
... }
WHEN user makes request PUT /projects/website with a valid token
... and body
... {
... "project": "website",
- ... "pipelines": [
- ... {
- ... "name": "build",
- ... "actions": [
- ... { "shell": "build-it" }
- ... ]
- ... }
- ... ]
+ ... "parameters": {"foo": "bar"},
+ ... "pipelines": ["build"]
... }
THEN result has status code 200
AND body matches
... {
... "project": "website",
- ... "pipelines": [
- ... {
- ... "name": "build",
- ... "actions": [
- ... { "shell": "build-it" }
- ... ]
- ... }
- ... ]
+ ... "parameters": {"foo": "bar"},
+ ... "pipelines": ["build"]
... }
AND controller state directory contains project website
@@ -188,14 +135,8 @@ Creating a new project with the same name is forbidden.
AND body matches
... {
... "project": "website",
- ... "pipelines": [
- ... {
- ... "name": "build",
- ... "actions": [
- ... { "shell": "build-it" }
- ... ]
- ... }
- ... ]
+ ... "parameters": {"foo": "bar"},
+ ... "pipelines": ["build"]
... }
WHEN user makes request DELETE /projects/website
diff --git a/yarns/150-pipelines.yarn b/yarns/150-pipelines.yarn
new file mode 100644
index 0000000..f0f678e
--- /dev/null
+++ b/yarns/150-pipelines.yarn
@@ -0,0 +1,168 @@
+<!--
+
+Copyright 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/>.
+
+-->
+
+# Controller pipeline management
+
+The Ick2 controller manages information about named pipelines.
+Pipelines are sequences of steps to achieve part of a project build.
+They're described like resources like this:
+
+ EXAMPLE pipeline resource
+ {
+ "name": "build-website",
+ "parameters": {
+ "foo": "bar"
+ },
+ "actions": [
+ {
+ "shell": "git clone git://git.liw.fi/ick2-website src" }
+ },
+ {
+ "shell": "cd src && ikiwiki --setup ikiwiki.setup"
+ },
+ {
+ "shell": "cd html && rsync -a --delete . server::/srv/http/ick2/."
+ }
+ ]
+ }
+
+In other words, there are several things that define a pipeline:
+
+* The `name`. This is used for referreing to the pipeline in the API.
+* A set of parameters, which are currently ignored.
+* A sequence of actions. At the moment, each action is a shell
+ command to be run, but that will change later.
+
+## Managing pipelines
+
+First we test the controller API for managing pipelines, without
+running them. We start by starting an instance of the controller.
+
+ SCENARIO managing pipelines
+ GIVEN an RSA key pair for token signing
+ AND an access token for user with scopes
+ ... uapi_pipelines_get
+ ... uapi_pipelines_post
+ ... uapi_pipelines_id_get
+ ... uapi_pipelines_id_put
+ ... uapi_pipelines_id_delete
+ AND controller config uses statedir at the state directory
+ AND a running ick controller
+
+ WHEN user makes request GET /pipelines
+ THEN result has status code 200
+ AND body matches { "pipelines": [] }
+
+ WHEN user makes request POST /pipelines with a valid token and body
+ ... {
+ ... "name": "build_website",
+ ... "actions": [
+ ... { "shell": "git clone git://repo src" },
+ ... { "shell": "mkdir html" },
+ ... { "shell": "ikiwiki src html" }
+ ... ]
+ ... }
+ THEN result has status code 201
+ AND body matches
+ ... {
+ ... "name": "build_website",
+ ... "actions": [
+ ... { "shell": "git clone git://repo src" },
+ ... { "shell": "mkdir html" },
+ ... { "shell": "ikiwiki src html" }
+ ... ]
+ ... }
+
+Creating a new pipeline with the same name is forbidden.
+
+ WHEN user makes request POST /pipelines with a valid token and body
+ ... {
+ ... "name": "build_website"
+ ... }
+ THEN result has status code 409
+
+ WHEN user makes request GET /pipelines
+ THEN result has status code 200
+ AND body matches
+ ... {
+ ... "pipelines": [
+ ... {
+ ... "name": "build_website",
+ ... "actions": [
+ ... { "shell": "git clone git://repo src" },
+ ... { "shell": "mkdir html" },
+ ... { "shell": "ikiwiki src html" }
+ ... ]
+ ... }
+ ... ]
+ ... }
+
+ WHEN user stops ick controller
+ GIVEN a running ick controller
+ WHEN user makes request GET /pipelines/build_website
+ THEN result has status code 200
+ AND body matches
+ ... {
+ ... "name": "build_website",
+ ... "actions": [
+ ... { "shell": "git clone git://repo src" },
+ ... { "shell": "mkdir html" },
+ ... { "shell": "ikiwiki src html" }
+ ... ]
+ ... }
+
+ WHEN user makes request PUT /pipelines/build_websitte with a valid token
+ ... and body
+ ... {
+ ... "name": "build_website",
+ ... "actions": [
+ ... { "shell": "build-it" }
+ ... ]
+ ... }
+ THEN result has status code 200
+ AND body matches
+ ... {
+ ... "name": "build_website",
+ ... "actions": [
+ ... { "shell": "build-it" }
+ ... ]
+ ... }
+
+ WHEN user makes request GET /pipelines/build_website
+ THEN result has status code 200
+ AND body matches
+ ... {
+ ... "name": "build_website",
+ ... "actions": [
+ ... { "shell": "build-it" }
+ ... ]
+ ... }
+
+ WHEN user makes request DELETE /pipelines/build_website
+ THEN result has status code 200
+ WHEN user makes request GET /pipelines/build_website
+ THEN result has status code 404
+
+ WHEN user makes request PUT /pipelines/doesnotexist with a valid token and body
+ ... {
+ ... "name": "doesnotexist"
+ ... }
+ THEN result has status code 404
+
+ FINALLY stop ick controller
diff --git a/yarns/400-build.yarn b/yarns/400-build.yarn
index a07eda4..ec3e8e5 100644
--- a/yarns/400-build.yarn
+++ b/yarns/400-build.yarn
@@ -28,6 +28,7 @@ Set up the controller.
GIVEN an RSA key pair for token signing
AND controller config uses statedir at the state directory
AND an access token for user with scopes
+ ... uapi_pipelines_post
... uapi_projects_post
... uapi_projects_id_pipelines_id_put
... uapi_projects_id_pipelines_id_get
@@ -38,23 +39,24 @@ Set up the controller.
... uapi_logs_id_get
AND a running ick controller
-Add up a project.
+Add up a project with some named pipelines.
+ WHEN user makes request POST /pipelines with a valid token and body
+ ... {
+ ... "name": "construct",
+ ... "actions": [
+ ... { "shell": "day 1" },
+ ... { "shell": "day 2" }
+ ... ]
+ ... }
+ THEN result has status code 201
WHEN user makes request POST /projects with a valid token and body
... {
... "project": "rome",
... "parameters": {
... "foo": "bar"
... },
- ... "pipelines": [
- ... {
- ... "name": "construct",
- ... "actions": [
- ... { "shell": "day 1" },
- ... { "shell": "day 2" }
- ... ]
- ... }
- ... ]
+ ... "pipelines": ["construct"]
... }
THEN result has status code 201
@@ -66,14 +68,7 @@ Add a second project so we know each project gets its own work steps.
... "parameters": {
... "hey": "there"
... },
- ... "pipelines": [
- ... {
- ... "name": "construct",
- ... "actions": [
- ... { "shell": "fork" }
- ... ]
- ... }
- ... ]
+ ... "pipelines": ["construct"]
... }
THEN result has status code 201
diff --git a/yarns/500-build-fail.yarn b/yarns/500-build-fail.yarn
index 2f5d79c..f0afa32 100644
--- a/yarns/500-build-fail.yarn
+++ b/yarns/500-build-fail.yarn
@@ -29,6 +29,7 @@ Set up the controller.
GIVEN an RSA key pair for token signing
AND controller config uses statedir at the state directory
AND an access token for user with scopes
+ ... uapi_pipelines_post
... uapi_projects_post
... uapi_projects_id_pipelines_id_put
... uapi_projects_id_pipelines_id_get
@@ -39,20 +40,21 @@ Set up the controller.
... uapi_logs_id_get
AND a running ick controller
-Add up a project.
+Add up a project and its pipelines.
+ WHEN user makes request POST /pipelines with a valid token and body
+ ... {
+ ... "name": "construct",
+ ... "actions": [
+ ... { "shell": "day 1" },
+ ... { "shell": "day 2" }
+ ... ]
+ ... }
+ THEN result has status code 201
WHEN user makes request POST /projects with a valid token and body
... {
... "project": "rome",
- ... "pipelines": [
- ... {
- ... "name": "construct",
- ... "actions": [
- ... { "shell": "day 1" },
- ... { "shell": "day 2" }
- ... ]
- ... }
- ... ]
+ ... "pipelines": ["construct"]
... }
THEN result has status code 201