diff options
author | Lars Wirzenius <liw@liw.fi> | 2018-01-19 19:41:04 +0200 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2018-01-19 19:41:04 +0200 |
commit | fbc669358923665491267363fb77a6a33648e7f4 (patch) | |
tree | eb842e5b379eed9b6d4c97481d06c775cc40f666 | |
parent | 4231981eb38f8764ebbc72c2044d3c7e4932011e (diff) | |
parent | 452fb6fd6664231814bcca13f60a7aef308ed9b6 (diff) | |
download | ick2-fbc669358923665491267363fb77a6a33648e7f4.tar.gz |
Merge branch 'liw/buildids'
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | ick2/apibase.py | 15 | ||||
-rw-r--r-- | ick2/buildsapi.py | 31 | ||||
-rw-r--r-- | ick2/logapi.py | 31 | ||||
-rw-r--r-- | ick2/projectapi.py | 8 | ||||
-rw-r--r-- | ick2/projectapi_tests.py | 11 | ||||
-rw-r--r-- | ick2/state.py | 15 | ||||
-rw-r--r-- | ick2/workapi.py | 10 | ||||
-rw-r--r-- | ick2/workapi_tests.py | 16 | ||||
-rw-r--r-- | yarns/100-projects.yarn | 15 | ||||
-rw-r--r-- | yarns/400-build.yarn | 258 | ||||
-rw-r--r-- | yarns/500-build-fail.yarn | 24 | ||||
-rw-r--r-- | yarns/900-implements.yarn | 27 | ||||
-rw-r--r-- | yarns/900-local.yarn | 8 | ||||
-rw-r--r-- | yarns/lib.py | 6 |
15 files changed, 382 insertions, 96 deletions
@@ -25,6 +25,9 @@ Version 0.22+git, not yet released * `icktool status` output now has a new column to indicate latest build status. +* Builds are now named `foo/123` (build 123 of project foo). Logs + similarly. + Version 0.22, released 2018-01-15 ---------------------------------- diff --git a/ick2/apibase.py b/ick2/apibase.py index c0b1aa5..d02e3a1 100644 --- a/ick2/apibase.py +++ b/ick2/apibase.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Lars Wirzenius +# Copyright (C) 2017-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 @@ -157,25 +157,30 @@ class ResourceApiBase(APIbase): return self._state.get_resource(self._type_name, name) def create(self, body, **kwargs): - name = self.get_resource_name(body) + resource = self.mangle_resource(body) + name = self.get_resource_name(resource) try: self._state.get_resource(self._type_name, name) except ick2.NotFound: - return self._state.add_resource(self._type_name, name, body) + return self._state.add_resource(self._type_name, name, resource) else: raise ick2.ExistsAlready(name) + def mangle_resource(self, resource): # pragma: no cover + return resource + def get_resource_name(self, resource): # pragma: no cover raise NotImplementedError() def update(self, body, name, **kwargs): - name = self.get_resource_name(body) + resource = self.mangle_resource(body) + name = self.get_resource_name(resource) try: self._state.get_resource(self._type_name, name) except ick2.NotFound: raise else: - return self._state.update_resource(self._type_name, name, body) + return self._state.update_resource(self._type_name, name, resource) def delete(self, name, **kwargs): self._state.remove_resource(self._type_name, name) diff --git a/ick2/buildsapi.py b/ick2/buildsapi.py index 9ee00b7..39319e4 100644 --- a/ick2/buildsapi.py +++ b/ick2/buildsapi.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Lars Wirzenius +# Copyright (C) 2017-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 @@ -21,6 +21,35 @@ class BuildsAPI(ick2.ResourceApiBase): # pragma: no cover def __init__(self, state): super().__init__('builds', state) + def get_routes(self, path): + return [ + { + 'method': 'POST', + 'path': path, + 'callback': self.POST(self.create), + }, + { + 'method': 'GET', + 'path': path, + 'callback': self.GET(self.list), + }, + { + 'method': 'GET', + 'path': '{}/<name:path>'.format(path), + 'callback': self.GET(self.show), + }, + { + 'method': 'PUT', + 'path': '{}/<name:path>'.format(path), + 'callback': self.PUT(self.update), + }, + { + 'method': 'DELETE', + 'path': '{}/<name:path>'.format(path), + 'callback': self.DELETE(self.delete), + }, + ] + def get_resource_name(self, resource): return resource['build'] diff --git a/ick2/logapi.py b/ick2/logapi.py index 37bf6aa..a59cd13 100644 --- a/ick2/logapi.py +++ b/ick2/logapi.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Lars Wirzenius +# Copyright (C) 2017-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 @@ -21,6 +21,35 @@ class LogAPI(ick2.ResourceApiBase): # pragma: no cover def __init__(self, state): super().__init__('log', state) + def get_routes(self, path): + return [ + { + 'method': 'POST', + 'path': path, + 'callback': self.POST(self.create), + }, + { + 'method': 'GET', + 'path': path, + 'callback': self.GET(self.list), + }, + { + 'method': 'GET', + 'path': '{}/<name:path>'.format(path), + 'callback': self.GET(self.show), + }, + { + 'method': 'PUT', + 'path': '{}/<name:path>'.format(path), + 'callback': self.PUT(self.update), + }, + { + 'method': 'DELETE', + 'path': '{}/<name:path>'.format(path), + 'callback': self.DELETE(self.delete), + }, + ] + def get_resource_name(self, resource): return resource['log'] diff --git a/ick2/projectapi.py b/ick2/projectapi.py index 6952d6d..ffa1f1b 100644 --- a/ick2/projectapi.py +++ b/ick2/projectapi.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Lars Wirzenius +# Copyright (C) 2017-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 @@ -22,6 +22,12 @@ class ProjectAPI(ick2.ResourceApiBase): super().__init__('projects', state) self._pi = ick2.PipelineInstances(self.get_state()) + def mangle_resource(self, resource): + new = dict(resource) + if 'next_build_id' not in new: + new['next_build_id'] = None + return new + def get_resource_name(self, resource): return resource['project'] diff --git a/ick2/projectapi_tests.py b/ick2/projectapi_tests.py index 4cca9e2..96242ef 100644 --- a/ick2/projectapi_tests.py +++ b/ick2/projectapi_tests.py @@ -58,12 +58,14 @@ class ProjectAPITests(unittest.TestCase): 'pipelines': ['build'], 'parameters': { 'foo': 'bar', - } + }, } api = self.create_api() - self.assertEqual(api.create(project), project) - self.assertEqual(api.list(), {'projects': [project]}) + new = api.create(project) + project['next_build_id'] = None + self.assertEqual(new, project) + self.assertEqual(api.list(), {'projects': [new]}) self.assertEqual(api.get_pipeline('foo', 'build'), {'status': 'idle'}) self.assertEqual(api.get_pipeline('foo', 'build'), {'status': 'idle'}) @@ -90,6 +92,7 @@ class ProjectAPITests(unittest.TestCase): project = { 'project': 'foo', 'pipelines': ['build'], + 'next_build_id': None, } api = self.create_api() api.create(project) @@ -101,6 +104,7 @@ class ProjectAPITests(unittest.TestCase): project = { 'project': 'foo', 'pipelines': ['build'], + 'next_build_id': None, } api = self.create_api() api.create(project) @@ -110,6 +114,7 @@ class ProjectAPITests(unittest.TestCase): project_v1 = { 'project': 'foo', 'pipelines': ['build'], + 'next_build_id': None, } project_v2 = dict(project_v1) project_v2['shell_steps'] = ['build it using magic'] diff --git a/ick2/state.py b/ick2/state.py index c7bed19..846b113 100644 --- a/ick2/state.py +++ b/ick2/state.py @@ -14,6 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. +import base64 import glob import os @@ -42,7 +43,9 @@ class ControllerState: def get_resource_filename(self, type_name, resource_name): dirname = self.get_resource_directory(type_name) - return os.path.join(dirname, resource_name + '.yaml') + basename = base64.urlsafe_b64encode( + resource_name.encode()).decode('ascii') + return os.path.join(dirname, basename + '.yaml') def load_resources(self, type_name): assert self._statedir is not None @@ -63,7 +66,15 @@ class ControllerState: def get_resource(self, type_name, resource_name): filename = self.get_resource_filename(type_name, resource_name) if os.path.exists(filename): - return self.load_resource(filename) + result = self.load_resource(filename) + ick2.log.log( + 'debug', msg_text='get_resource called', + type_name=type_name, resource_name=resource_name, + filename=filename, result=result) + return result + ick2.log.log( + 'warning', msg_text='Cannot find resource', + type_name=type_name, resource_name=resource_name) raise NotFound() def add_resource(self, type_name, resource_name, resource): diff --git a/ick2/workapi.py b/ick2/workapi.py index e12b0d8..67fad81 100644 --- a/ick2/workapi.py +++ b/ick2/workapi.py @@ -50,8 +50,14 @@ class WorkAPI(ick2.APIbase): pipeline['status'] = 'building' self._update_pipeline(project, pipeline) - build_id = project.get('build_id', 0) + 1 - project['build_id'] = build_id + next_build_id = project.get('next_build_id') + if next_build_id is None: + next_build_id = 1 + build_id = '{}/{}'.format(project['project'], next_build_id) + ick2.log.log( + 'info', msg_text='build id chosen', + old_next_build_id=next_build_id, chosen_build_id=build_id) + project['next_build_id'] = next_build_id + 1 self._projects.update_project(project) self._start_build(project, pipeline, worker, build_id) diff --git a/ick2/workapi_tests.py b/ick2/workapi_tests.py index 049e17c..fdfc601 100644 --- a/ick2/workapi_tests.py +++ b/ick2/workapi_tests.py @@ -79,7 +79,7 @@ class WorkAPITests(unittest.TestCase): self.create_worker_api() work = self.create_work_api() expected = { - 'build_id': 1, + 'build_id': 'foo/1', 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', @@ -89,7 +89,7 @@ class WorkAPITests(unittest.TestCase): 'step': { 'action': 'create_workspace', }, - 'log': '/logs/1', + 'log': '/logs/foo/1', } self.assertEqual(work.get_work('asterix'), expected) @@ -104,7 +104,7 @@ class WorkAPITests(unittest.TestCase): # Ask for some work. expected = { - 'build_id': 1, + 'build_id': 'foo/1', 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', @@ -114,13 +114,13 @@ class WorkAPITests(unittest.TestCase): 'step': { 'action': 'create_workspace', }, - 'log': '/logs/1', + 'log': '/logs/foo/1', } self.assertEqual(work.get_work('asterix'), expected) # Post a partial update. done = { - 'build_id': 1, + 'build_id': 'foo/1', 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', @@ -171,7 +171,7 @@ class WorkAPITests(unittest.TestCase): # Ask for some work. expected = { - 'build_id': 1, + 'build_id': 'foo/1', 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', @@ -181,13 +181,13 @@ class WorkAPITests(unittest.TestCase): 'step': { 'action': 'create_workspace', }, - 'log': '/logs/1', + 'log': '/logs/foo/1', } self.assertEqual(work.get_work('asterix'), expected) # Post a partial update. done = { - 'build_id': 1, + 'build_id': 'foo/1', 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', diff --git a/yarns/100-projects.yarn b/yarns/100-projects.yarn index eed83eb..63572e7 100644 --- a/yarns/100-projects.yarn +++ b/yarns/100-projects.yarn @@ -79,7 +79,8 @@ building them. We start by starting an instance of the controller. AND body matches ... { ... "project": "website", - ... "pipelines": ["build"] + ... "pipelines": ["build"], + ... "next_build_id": null ... } AND controller state directory contains project website @@ -99,7 +100,8 @@ Creating a new project with the same name is forbidden. ... "projects": [ ... { ... "project": "website", - ... "pipelines": ["build"] + ... "pipelines": ["build"], + ... "next_build_id": null ... } ... ] ... } @@ -111,7 +113,8 @@ Creating a new project with the same name is forbidden. AND body matches ... { ... "project": "website", - ... "pipelines": ["build"] + ... "pipelines": ["build"], + ... "next_build_id": null ... } WHEN user makes request PUT /projects/website with a valid token @@ -126,7 +129,8 @@ Creating a new project with the same name is forbidden. ... { ... "project": "website", ... "parameters": {"foo": "bar"}, - ... "pipelines": ["build"] + ... "pipelines": ["build"], + ... "next_build_id": null ... } AND controller state directory contains project website @@ -136,7 +140,8 @@ Creating a new project with the same name is forbidden. ... { ... "project": "website", ... "parameters": {"foo": "bar"}, - ... "pipelines": ["build"] + ... "pipelines": ["build"], + ... "next_build_id": null ... } WHEN user makes request DELETE /projects/website diff --git a/yarns/400-build.yarn b/yarns/400-build.yarn index f30e942..e28bccf 100644 --- a/yarns/400-build.yarn +++ b/yarns/400-build.yarn @@ -115,8 +115,8 @@ the worker to construct a new workspace for the build. THEN result has status code 200 AND body matches ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -132,8 +132,8 @@ the worker to construct a new workspace for the build. THEN result has status code 200 AND body matches ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -160,8 +160,8 @@ User can now see pipeline is running and which worker is building it. ... { ... "worker": "obelix", ... "doing": { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -180,8 +180,8 @@ User can now see pipeline is running and which worker is building it. ... { ... "builds": [ ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -195,12 +195,12 @@ User can now see pipeline is running and which worker is building it. ... "foo": "bar" ... }, ... "status": "building", - ... "log": "/logs/1" + ... "log": "/logs/rome/1" ... } ... ] ... } - WHEN user makes request GET /logs/1 + WHEN user makes request GET /logs/rome/1 THEN result has status code 200 AND result has header Content-Type: text/plain AND body text is "" @@ -209,7 +209,7 @@ Worker reports workspace creation is done. Note the zero exit code. WHEN worker-manager makes request POST /work with a valid token and body ... { - ... "build_id": 1, + ... "build_id": "rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -226,8 +226,8 @@ Worker requests more work, and gets the first actual build step. THEN result has status code 200 AND body matches ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -244,7 +244,7 @@ hasn't finished yet. WHEN worker-manager makes request POST /work with a valid token and body ... { - ... "build_id": 1, + ... "build_id": "rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -262,8 +262,8 @@ didnt't finish. THEN result has status code 200 AND body matches ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -277,7 +277,7 @@ didnt't finish. The build log is immediately accessible. - WHEN user makes request GET /logs/1 + WHEN user makes request GET /logs/rome/1 THEN result has status code 200 AND result has header Content-Type: text/plain AND body text is "hey ho" @@ -286,7 +286,7 @@ Report the step is done, and successfully. WHEN worker-manager makes request POST /work with a valid token and body ... { - ... "build_id": 1, + ... "build_id": "rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -297,7 +297,7 @@ Report the step is done, and successfully. ... } THEN result has status code 201 - WHEN user makes request GET /logs/1 + WHEN user makes request GET /logs/rome/1 THEN result has status code 200 AND result has header Content-Type: text/plain AND body text is "hey ho, hey ho\n" @@ -310,8 +310,8 @@ The build status now shows the next step as the active one. ... { ... "builds": [ ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -325,7 +325,7 @@ The build status now shows the next step as the active one. ... "foo": "bar" ... }, ... "status": "building", - ... "log": "/logs/1" + ... "log": "/logs/rome/1" ... } ... ] ... } @@ -336,8 +336,8 @@ Now there's another step to do. THEN result has status code 200 AND body matches ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -357,7 +357,7 @@ User sees changed status. ... { ... "worker": "obelix", ... "doing": { - ... "build_id": 1, + ... "build_id": "rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -367,7 +367,7 @@ User sees changed status. ... "step": { ... "shell": "day 2" ... }, - ... "log": "/logs/1" + ... "log": "/logs/rome/1" ... } ... } @@ -375,7 +375,7 @@ Report it done. WHEN worker-manager makes request POST /work with a valid token and body ... { - ... "build_id": 1, + ... "build_id": "rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -407,8 +407,8 @@ no current action. ... { ... "builds": [ ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -421,18 +421,17 @@ no current action. ... "parameters": { ... "foo": "bar" ... }, - ... "status": 0, - ... "log": "/logs/1" + ... "status": 0 ... } ... ] ... } - WHEN user makes request GET /builds/1 + WHEN user makes request GET /builds/rome/1 THEN result has status code 200 AND body matches ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -445,11 +444,10 @@ no current action. ... "parameters": { ... "foo": "bar" ... }, - ... "status": 0, - ... "log": "/logs/1" + ... "status": 0 ... } - WHEN user makes request GET /logs/1 + WHEN user makes request GET /logs/rome/1 THEN result has status code 200 AND result has header Content-Type: text/plain AND body text is "hey ho, hey ho\nto the gold mine we go!\n" @@ -464,8 +462,8 @@ Start build again. This should become build number 2. THEN result has status code 200 AND body matches ... { - ... "build_id": 2, - ... "log": "/logs/2", + ... "build_id": "rome/2", + ... "log": "/logs/rome/2", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -483,8 +481,8 @@ Start build again. This should become build number 2. ... { ... "builds": [ ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -500,8 +498,8 @@ Start build again. This should become build number 2. ... "status": 0 ... }, ... { - ... "build_id": 2, - ... "log": "/logs/2", + ... "build_id": "rome/2", + ... "log": "/logs/rome/2", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -521,7 +519,7 @@ Start build again. This should become build number 2. WHEN worker-manager makes request POST /work with a valid token and body ... { - ... "build_id": 2, + ... "build_id": "rome/2", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -536,8 +534,8 @@ Start build again. This should become build number 2. THEN result has status code 200 AND body matches ... { - ... "build_id": 2, - ... "log": "/logs/2", + ... "build_id": "rome/2", + ... "log": "/logs/rome/2", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -551,7 +549,7 @@ Start build again. This should become build number 2. WHEN worker-manager makes request POST /work with a valid token and body ... { - ... "build_id": 2, + ... "build_id": "rome/2", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -567,7 +565,7 @@ Start build again. This should become build number 2. WHEN worker-manager makes request POST /work with a valid token and body ... { - ... "build_id": 2, + ... "build_id": "rome/2", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -584,8 +582,8 @@ Start build again. This should become build number 2. ... { ... "builds": [ ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -601,8 +599,8 @@ Start build again. This should become build number 2. ... "status": 0 ... }, ... { - ... "build_id": 2, - ... "log": "/logs/2", + ... "build_id": "rome/2", + ... "log": "/logs/rome/2", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -621,3 +619,159 @@ Start build again. This should become build number 2. ... } FINALLY stop ick controller + + +# Build two projects sequentially + +This scenario tests the controller API to simulate a build. + + SCENARIO build two projects sequentially + +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 + ... uapi_projects_id_builds_get + ... uapi_workers_id_get + ... uapi_builds_get + ... uapi_builds_id_get + ... uapi_logs_id_get + AND a running ick controller + +Add a couple of projects. + + WHEN user makes request POST /pipelines with a valid token and body + ... { + ... "pipeline": "do_something", + ... "actions": [ + ... { "shell": "something" } + ... ] + ... } + THEN result has status code 201 + WHEN user makes request POST /projects with a valid token and body + ... { + ... "project": "first", + ... "pipelines": ["do_something"] + ... } + THEN result has status code 201 + WHEN user makes request POST /projects with a valid token and body + ... { + ... "project": "second", + ... "pipelines": ["do_something"] + ... } + THEN result has status code 201 + +Register a worker. + + GIVEN an access token for worker-manager with scopes + ... uapi_workers_post + ... uapi_work_post + ... uapi_work_id_get + WHEN worker-manager makes request POST /workers with a valid token and body + ... { + ... "worker": "obelix" + ... } + THEN result has status code 201 + +Build the first project. + + WHEN user makes request PUT /projects/first/pipelines/do_something + ... with a valid token and body { "status": "triggered" } + THEN result has status code 200 + + WHEN worker-manager makes request GET /work/obelix + THEN result is step + ... { + ... "action": "create_workspace" + ... } + + WHEN worker-manager makes request POST /work with a valid token and body + ... { + ... "build_id": "first/1", + ... "worker": "obelix", + ... "project": "first", + ... "pipeline": "do_something", + ... "exit_code": 0, + ... "stdout": "", + ... "stderr": "", + ... "timestamp": "2017-10-27T17:08:49" + ... } + THEN result has status code 201 + + WHEN worker-manager makes request GET /work/obelix + THEN result is step + ... { + ... "shell": "something" + ... } + + WHEN worker-manager makes request POST /work with a valid token and body + ... { + ... "build_id": "first/1", + ... "worker": "obelix", + ... "project": "first", + ... "pipeline": "do_something", + ... "exit_code": 0, + ... "stdout": "", + ... "stderr": "", + ... "timestamp": "2017-10-27T17:08:49" + ... } + THEN result has status code 201 + + WHEN user requests list of builds + THEN the list of builds is ["first/1"] + +Build second project. + + WHEN user makes request PUT /projects/second/pipelines/do_something + ... with a valid token and body { "status": "triggered" } + THEN result has status code 200 + + WHEN worker-manager makes request GET /work/obelix + THEN result is step + ... { + ... "action": "create_workspace" + ... } + + WHEN worker-manager makes request POST /work with a valid token and body + ... { + ... "build_id": "second/1", + ... "worker": "obelix", + ... "project": "second", + ... "pipeline": "do_something", + ... "exit_code": 0, + ... "stdout": "", + ... "stderr": "", + ... "timestamp": "2017-10-27T17:08:49" + ... } + THEN result has status code 201 + + WHEN worker-manager makes request GET /work/obelix + THEN result is step + ... { + ... "shell": "something" + ... } + + WHEN worker-manager makes request POST /work with a valid token and body + ... { + ... "build_id": "second/1", + ... "worker": "obelix", + ... "project": "second", + ... "pipeline": "do_something", + ... "exit_code": 0, + ... "stdout": "", + ... "stderr": "", + ... "timestamp": "2017-10-27T17:08:49" + ... } + THEN result has status code 201 + + WHEN user requests list of builds + THEN the list of builds is ["first/1", "second/1"] + +Finish up. + + FINALLY stop ick controller diff --git a/yarns/500-build-fail.yarn b/yarns/500-build-fail.yarn index 64e8333..39a2e32 100644 --- a/yarns/500-build-fail.yarn +++ b/yarns/500-build-fail.yarn @@ -82,8 +82,8 @@ Worker wants work and gets the first step to run. THEN result has status code 200 AND body matches ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -98,7 +98,7 @@ failure. WHEN worker-manager makes request POST /work with a valid token and body ... { - ... "build_id": 1, + ... "build_id": "rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -140,8 +140,8 @@ Also, there's a build with a log. ... { ... "builds": [ ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -152,18 +152,17 @@ Also, there's a build with a log. ... ], ... "current_action": null, ... "parameters": {}, - ... "status": 1, - ... "log": "/logs/1" + ... "status": 1 ... } ... ] ... } - WHEN user makes request GET /builds/1 + WHEN user makes request GET /builds/rome/1 THEN result has status code 200 AND body matches ... { - ... "build_id": 1, - ... "log": "/logs/1", + ... "build_id": "rome/1", + ... "log": "/logs/rome/1", ... "worker": "obelix", ... "project": "rome", ... "pipeline": "construct", @@ -174,11 +173,10 @@ Also, there's a build with a log. ... ], ... "current_action": null, ... "parameters": {}, - ... "status": 1, - ... "log": "/logs/1" + ... "status": 1 ... } - WHEN user makes request GET /logs/1 + WHEN user makes request GET /logs/rome/1 THEN result has status code 200 AND result has header Content-Type: text/plain AND body text is "eek!" diff --git a/yarns/900-implements.yarn b/yarns/900-implements.yarn index 39b590b..b27b830 100644 --- a/yarns/900-implements.yarn +++ b/yarns/900-implements.yarn @@ -139,3 +139,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. value = get_next_match() headers = vars['headers'] assertEqual(headers[name].lower(), value.lower()) + + IMPLEMENTS THEN result is step (.+) + step = json.loads(get_next_match()) + body = json.loads(vars['body']) + actual_step = body['step'] + print('expected step', step) + print('actual body', body) + print('actual step', actual_step) + diff = dict_diff(step, actual_step) + print('diff', diff) + assertEqual(diff, None) + + IMPLEMENTS WHEN (\S+) requests list of builds + user = get_next_match() + token = get_token(user) + url = vars['url'] + path = '/builds' + http(vars, get, url + path, token=token) + + IMPLEMENTS THEN the list of builds is (.+) + expected = json.loads(get_next_match()) + print('expected', expected) + body = json.loads(vars['body']) + print('body', body) + actual = [o['build_id'] for o in body['builds']] + print('actual', actual) + assertEqual(actual, expected) diff --git a/yarns/900-local.yarn b/yarns/900-local.yarn index 89652de..2739542 100644 --- a/yarns/900-local.yarn +++ b/yarns/900-local.yarn @@ -1,6 +1,6 @@ <!-- -Copyright 2017 Lars Wirzenius +Copyright 2017-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 @@ -93,12 +93,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. IMPLEMENTS THEN controller state directory contains project (\S+) name = get_next_match() - filename = os.path.join(vars['statedir'], 'projects', name + '.yaml') + basename = encode_basename(name) + filename = os.path.join(vars['statedir'], 'projects', basename + '.yaml') assertTrue(os.path.exists(filename)) IMPLEMENTS THEN controller state directory contains worker (\S+) name = get_next_match() - filename = os.path.join(vars['statedir'], 'workers', name + '.yaml') + basename = encode_basename(name) + filename = os.path.join(vars['statedir'], 'workers', basename + '.yaml') assertTrue(os.path.exists(filename)) ## Start and stop blob service diff --git a/yarns/lib.py b/yarns/lib.py index cd95b6e..0914b00 100644 --- a/yarns/lib.py +++ b/yarns/lib.py @@ -13,6 +13,8 @@ # 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 base64 import errno import json import os @@ -218,3 +220,7 @@ def list_diff(a, b): if delta: return '\n'.join(delta) return None + + +def encode_basename(basename): + return base64.urlsafe_b64encode(basename.encode()).decode('ascii') |