diff options
author | Lars Wirzenius <liw@liw.fi> | 2017-08-10 17:56:49 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2017-08-10 17:56:49 +0300 |
commit | 308f4db410e3c68eac453be107f8d6207814280d (patch) | |
tree | 5de939a42bc0e91246f7c424565edb644f29fe91 /arch | |
parent | 4a9db190bea38a6577c15950fefc939b1f52c6ef (diff) | |
download | ick2-308f4db410e3c68eac453be107f8d6207814280d.tar.gz |
Add: some architecture documentation for ALPHA-1
Diffstat (limited to 'arch')
-rw-r--r-- | arch/build.py | 78 | ||||
-rwxr-xr-x | arch/build.sh | 18 | ||||
-rw-r--r-- | arch/ick2-arch.mdwn | 306 | ||||
-rw-r--r-- | arch/ick2.css | 79 |
4 files changed, 481 insertions, 0 deletions
diff --git a/arch/build.py b/arch/build.py new file mode 100644 index 0000000..d60be05 --- /dev/null +++ b/arch/build.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python2 + + +import os +import sys +import tempfile + +import cliapp + + +def parse_markdown(f, tmpdir): + lines = f.readlines() + result = [] + uml_filenames = [] + while lines: + prefix, uml, lines = extract_uml(lines) + result.extend(prefix) + if uml: + filename = create_uml_file(tmpdir, uml) + uml_filenames.append(filename) + result.extend(include(uml, filename)) + return result, uml_filenames + + +def extract_uml(lines): + start = find_line(lines, '@startuml\n') + if start is None: + return lines, None, [] + + end = find_line(lines, '@enduml\n') + if end is None: + return lines, None, [] + + return lines[:start], lines[start:end+1], lines[end+1:] + + +def find_line(lines, pattern): + for i, line in enumerate(lines): + if line.startswith(pattern): + return i + return None + + +def create_uml_file(tmpdir, lines): + fd, filename = tempfile.mkstemp(dir=tmpdir) + os.write(fd, ''.join(lines)) + os.close(fd) + return filename + + +def include(uml, filename): + title = get_title(uml) + return [ + '![{}]({}.png)\\ \n'.format(title, filename), + '\n', + ] + + +def get_title(uml): + i = find_line(uml, 'title ') + if i is not None: + return uml[i].split(' ', 1)[1] + + +def plantuml(filename): + cliapp.runcmd(['plantuml', '-tpng', '-output', '.', filename]) + + +def main(): + tmpdir = sys.argv[1] + lines, filenames = parse_markdown(sys.stdin, tmpdir) + sys.stdout.write(''.join(lines)) + for filename in filenames: + plantuml(filename) + + +if __name__ == '__main__': + main() diff --git a/arch/build.sh b/arch/build.sh new file mode 100755 index 0000000..034bd39 --- /dev/null +++ b/arch/build.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -eu + +output="$(dirname "$1")/$(basename "$1" .mdwn)" + +tmp="$(mktemp -d)" + +clean() +{ + rm -rf "$tmp" +} + +#trap clean EXIT + +cat -- "$@" | python2 ./build.py "$tmp" > "$tmp/foo.mdwn" +pandoc --toc -V documentstyle:report --chapters -o "$output.pdf" "$tmp/foo.mdwn" +pandoc --toc -o "$output.html" --number-sections --standalone -H ick2.css "$tmp/foo.mdwn" diff --git a/arch/ick2-arch.mdwn b/arch/ick2-arch.mdwn new file mode 100644 index 0000000..3234756 --- /dev/null +++ b/arch/ick2-arch.mdwn @@ -0,0 +1,306 @@ +--- +title: Ick2 architecture +author: Lars Wirzenius +date: work-in-progress for ALPHA-1 +... + + +Introduction +============================================================================= + +Ick2 consists of many micro services. This document describes how they +are used individually and together. + +* The controller keeps track of projects, build pipelines, workers, + and the current state of each. It decides what build step is next, + and who should execute it. +* Worker-manager represents a build host. It queries the controller + for work, and makes the build host (the actual worker) execute it, + and then reports results back to the worker. +* The trigger service decides when a build should start. +* The controller and trigger services provide an API. The identity + provider (IDP) takes care of the authentication of each API client, + and what privileges each should have. The API client authenticates + itself to the IDP, and receives an access token. The API provider + gets the token in each request, validates it, and inspects it to see + what the client is allowed to do. + +A major point of the IDP is to have just a single place where +authentication and authorisation is configured. + + +Individual API interactions +============================================================================= + +This chapter covers interactions with individual APIs. + + +Getting an access token +----------------------------------------------------------------------------- + +The API client (user's command line tool, a putative web app, git +server, worker-manager, etc) authenticates itself to the IDP, and if +successful, gets back a signed JSON Web Token. It will include the +token in all requests to all APIs so that the API provider will know +what the client is allowed to do. + +The privileges for each API client are set by the sysadmin who +installs the CI system, or a user who's been given IDP admin +privileges by the sysadmin. + +@startuml +hide footbox +title Get an access token +client -> IDP : GET /auth, with Basic Auth, over https +IDP --> client : signed JWT token +@enduml + +All API calls need a token. Getting a token happen the same way for +every API client. + + +Worker (worker-manager) registration +----------------------------------------------------------------------------- + +The sysadmin arranges to start a worker-manager for every build host. +They may run on the same host, or not: the Ick2 architecture doesn't +really care. If they run on the same host, the worker manager will +start a subprocess. If on different hosts, the subprocess will be +started using ssh. + +The CI admin may define tags for each worker. Attributes may include +things like whether the worker can be trusted with credentials for +logging into other workers, or for retrieving source code from the git +server. Workers may not override such tags. Workers may, however, +provide other tags, to e.g., report their CPU architecture or Debian +release. The controller will eventually be able to use the tags to +choose which worker should execute which pipeline steps. + +@startuml +hide footbox +title Register worker +worker_manager -> IDP : GET /auth, with Basic Auth, over https +IDP --> worker_manager : token A +worker_manager -> controller : POST /workers (token A) +controller --> worker_manager : success +@enduml + +The worker manager runs a very simple state machine. + +@startuml +title Worker-manager state machine + +Querying : ask controller for work +Running : run subprocess + + +[*] -down-> Idle : start +Idle -down-> Querying : short timeout has expired +Querying -up-> Idle : nothing to do +Querying --> Running : something to do + +Running --> Running : get output, report to controller +Running --> Idle : subprocess finished, report to controller +@enduml + + +Add project to controller +----------------------------------------------------------------------------- + +The CI admin (or a user authorised by the CI admin) adds projects to +the controller to allow them to be built. This is done using an "CI +administration application", which initially will be a command line +tool, but may later become a web application as well. Either way, the +controller provides API endpoints for this. + +@startuml +hide footbox +title Add project to controller + +adminapp -> IDP : GET /auth, with Basic Auth, over https +IDP --> adminapp : token B +adminapp -> controller : POST /projects (token B) +controller --> adminapp : success or failure indication +@enduml + + +A full build +============================================================================= + +Next we look at how the various compontens interact during a complete +build, using two workers: one trusted with credentials, one doing the +actual heavy work. We assume that workers have been registered and +projects added. + +The sequence diagrams in this chapter have been split into stages, to +make them easier to view and read. Each diagram after the first one +continues where the previous one left off. + +Trigger build by pushing changes to git server +----------------------------------------------------------------------------- + +@startuml +hide footbox +title Build triggered by git change + +developer -> gitano : git push + +gitano -> IDP : GET /auth, with Basic Auth, over https +IDP --> gitano : token C +gitano -> trigger : GET /git/website.git (token C) +note right + Git server notifies + trigger service that + a git repo has changed +end note + +||| + +trigger -> IDP : GET /auth, with Basic Auth, over https +IDP --> trigger : token D +trigger -> controller : GET /projects (token D) +note right + trigger service queries + controller to get list + of all projects, so it + knows which builds to + start +end note +controller --> trigger : list of projects + +||| + +trigger -> controller : GET /projects/website (token D) +note right + trigger service + gets project config + so it knows what + pipelines project has +end note +controller --> trigger : project description, incl. pipelines + +||| + +trigger -> controller : POST /projects/website/pipelines/getsource/+start (token D) +@enduml + +The first pipeline has now been started by the trigger service. + + +Pipeline 1: get sources +----------------------------------------------------------------------------- + +The first pipeline uses the trusted worker to fetch source code from +the git server (we assume that requires credentials), and push them +to the powerful worker. + +@startuml +hide footbox +title Build pipeline: get source + +trusty -> IDP : GET /auth, with Basic Auth, over https +IDP --> trusty : token E + +||| + +trusty -> controller : GET /worker/trusty (token E) +controller --> trusty : "clone website source into workspace" +trusty -> gitano : git clone +trusty -> controller : POST /worker/trusty, exit=0 (token E) + +||| + +trusty -> controller : GET /worker/trusty (token E) +controller --> trusty : "rsync to beefy" +trusty -> beefy : rsync +trusty -> controller : POST /worker/trusty, exit=0 (token E) + +||| + +trusty -> controller : GET /worker/trusty (token E) +controller -> trusty : "notify trigger service pipeline is finished" +trusty -> controller : POST /worker/trusty, exit=0 (token E) +note right + note that we need to tell controller this last step is finished + **before** telling the trigger service the pipeline is finished +end note +trusty -> trigger : GET /pipelines/website/getsource (token E) +trigger -> controller : GET /projects/website/pipelines/getsource (token D) +note right + Trigger service checks + status of pipeline +end note +trigger -> controller : POST /projects/website/pipelines/ikiwiki/+start (token D) +@enduml + +The first pipeline finished, and the website building can start. +That's the second pipeline, which has just been started. + + +Pipeline 2: Build static web site +----------------------------------------------------------------------------- + +The second pipeline runs on the beefy worker. The source is already +there and it just needs to perform the build. + +@startuml +hide footbox +title Build static website + +beefy -> IDP : GET /auth, with Basic Auth, over https +IDP --> beefy : token F +beefy -> controller : GET /worker/beefy (token F) +controller -> beefy : "build static website" +beefy -> beefy : run ikiwiki to build site +beefy -> controller : POST /worker/beefy, exit=0 (token F) + +||| + +beefy -> controller : GET /worker/beefy (token F) +controller -> beefy : "notify trigger service pipeline is finished" +beefy -> controller : POST /worker/beefy, exit=0 (token F) +beefy -> trigger : GET /pipelines/website/ikiwiki (token F) +trigger -> controller : GET /projects/website/pipelines/ikiwiki (token D) +trigger -> controller : POST /projects/website/pipelines/publish/+start (token D) + +@enduml + +At the end of the second pipeline, we start the third one. + +Pipeline 3: Publish web site to web server +----------------------------------------------------------------------------- + +The third pipeline copies the built static website from the beefy +worker, via the trusted worker, to the actual web server. + +@startuml +hide footbox +title Copy built site from beefy to web server + +trusty -> controller : GET /worker/trusty (token E) +controller -> trusty : "rsync workspace from beefy" +trusty -> beefy : rsync +beefy --> trusty : workspace updates +trusty -> controller : POST /worker/trusty, exit=0 (token E) + +||| + +trusty -> controller : GET /worker/trusty (token E) +controller -> trusty : "rsync static website to web server" +trusty -> webserver : rsync +trusty -> controller : POST /worker/trusty, exit=0 (token E) + +||| + +trusty -> controller : GET /worker/trusty (token E) +controller --> trusty : "notify trigger service pipeline is finished" +trusty -> controller : POST /worker/trusty, exit=0 (token E) +trusty -> trigger : GET /pipelines/website/publish (token E) +trigger -> controller : GET /projects/website/pipelines/ikiwiki (token D) +note right + There are no further pipelines. +end note + +@enduml + +The website is now built and published. diff --git a/arch/ick2.css b/arch/ick2.css new file mode 100644 index 0000000..78cd374 --- /dev/null +++ b/arch/ick2.css @@ -0,0 +1,79 @@ +<style> +html { + background: white; + font-family: serif; + margin-left: 3em; + margin-right: 2em; + margin-top: 2em; +} + +form#searchform { + font-family: monospace; + text-align: right; +} + +div.actions { + font-family: monospace; + text-align: right; +} + +div.actions ul, div.actions li { + display: inline; +} + +div.pageheader { + font-family: monospace; + margin-bottom: 2em; +} + +div.pageheader span.title { + display: block; + font-size: 200%; + font-weight: bold; + font-family: sans-serif; + margin-top: 0.5em; +} + +div#pagebody { +} + +div.pagefooter { + font-family: monospace; + margin-top: 3em; +} + +div#pagebody { +} + +div#TOC ul { + list-style: none; +} + +h1, h2, h3, h4, h5, h6 { + font-family: sans-serif; + font-weight: bold; + margin-top: 2em; +} + +h1 { + font-size: 150%; +} + +h2 { + font-size: 120%; +} + +h3 { + font-size: 100%; +} + +ul li, ol li { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +pre { + margin-left: 4em; +} + +</style> |