summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2018-04-20 19:23:00 +0300
committerLars Wirzenius <liw@liw.fi>2018-04-20 19:23:00 +0300
commit4bbb2ccbcdbdb055ffe0799361f3abd3cb5aeae7 (patch)
tree3119c70c36b784d3b9dc4b36e7ab4c7ce829c913
parentef7c00019943b693955c78fd182ac41c925687a9 (diff)
parent186ec3f89806fc3d5532b171c7ae34b06059c491 (diff)
downloadick2-4bbb2ccbcdbdb055ffe0799361f3abd3cb5aeae7.tar.gz
Merge: icktool improvements
-rw-r--r--NEWS6
-rw-r--r--ick2/client.py5
-rwxr-xr-xicktool201
3 files changed, 183 insertions, 29 deletions
diff --git a/NEWS b/NEWS
index c55a38c..52133c1 100644
--- a/NEWS
+++ b/NEWS
@@ -20,6 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
Version 0.41+git, not yet released
----------------------------------
+* `icktool status`, `icktool trigger` commands added.
+
+* `icktool build-graph PROJECT [BUILD-ID]` command added. It writes
+ out a .dot file for showing the build graph for a build. Feed that
+ to **dot**(1) for processing. Works for a running build as well as
+ older builds.
Version 0.41, released 2018-04-20
----------------------------------
diff --git a/ick2/client.py b/ick2/client.py
index bd0586e..008cfd7 100644
--- a/ick2/client.py
+++ b/ick2/client.py
@@ -218,9 +218,8 @@ class ControllerClient:
url = self.url(path)
return self._api.put(url, body=obj)
- def trigger(self, project_name, pipeline_name): # pragma: no cover
- path = '/projects/{}/pipelines/{}/+trigger'.format(
- project_name, pipeline_name)
+ def trigger(self, project_name): # pragma: no cover
+ path = '/projects/{}/+trigger'.format(project_name)
url = self.url(path)
return self._api.get_dict(url)
diff --git a/icktool b/icktool
index c54e3df..7be5bc1 100755
--- a/icktool
+++ b/icktool
@@ -18,6 +18,7 @@
import configparser
import json
import logging
+import os
import sys
import cliapp
@@ -73,15 +74,10 @@ class Icktool(cliapp.Application):
)
self.settings.string(
- ['auth-url'],
- 'use URL as the authentication URL',
- metavar='URL',
- )
-
- self.settings.string(
['secrets'],
- 'use URL as the controller base URL',
- metavar='URL',
+ 'use FILE for credentials for authentication server',
+ metavar='FILE',
+ default=os.path.expanduser('~/.config/icktool/credentials.conf')
)
self.settings.boolean(
@@ -113,21 +109,128 @@ class Icktool(cliapp.Application):
self.output.write('{}\n'.format(scope))
def cmd_token(self, args):
- token = self._new_token()
+ api = self._new_api()
+ token = self._new_token(api)
self.output.write('{}\n'.format(token))
def cmd_version(self, args):
- token = self._new_token()
api = self._new_api()
+ token = self._new_token(api)
api.set_token(token)
version = api.get_version()
self._prettyson(version)
+ def cmd_status(self, args):
+ table = Table()
+ table.set_columns('project', 'status', 'build_status', 'log_id')
+
+ api = self._new_api()
+ token = self._new_token(api)
+ api.set_token(token)
+
+ projects = api.show('/projects')
+ builds = api.show('/builds')
+
+ for project in projects['projects']:
+ project_name = project['project']
+ project_status = api.show(
+ '/projects/{}/status'.format(project_name))
+
+ row = {
+ 'project': project_name,
+ 'status': project_status['status'],
+ 'build_status': 'n/a',
+ 'log_id': 'n/a'
+ }
+
+ build = self._latest_build(project_name, builds)
+ if build:
+ row['build_status'] = build['status']
+ row['log_id'] = build['log']
+
+ table.append_row(**row)
+
+ self.output.write(table.format())
+
+ def _latest_build(self, project_name, builds):
+ builds = self._find_builds(project_name, builds)
+ if builds:
+ return builds[-1]
+ return None
+
+ def _find_builds(self, project_name, builds):
+ return [b for b in builds['builds'] if b['project'] == project_name]
+
+ def _find_build(self, builds, build_id):
+ for build in builds:
+ if build['build_id'] == build_id:
+ return build
+ return None
+
+ def cmd_build_graph(self, args):
+ api = self._new_api()
+ token = self._new_token(api)
+ api.set_token(token)
+
+ project_name = args[0]
+ if len(args) > 1:
+ build_id = args[1]
+ else:
+ build_id = 'latest'
+
+ builds = api.show('/builds')
+ builds = self._find_builds(project_name, builds)
+ if not builds:
+ sys.exit('No such build %s' % build_id)
+
+ if build_id == 'latest':
+ build = builds[-1]
+ else:
+ build = self._find_build(builds, build_id)
+
+ actions = build['actions']
+ current = build['current_action']
+ status = build['status']
+
+ f = self.output
+ f.write('digraph "build_graph" {\n')
+ for i, action in enumerate(actions):
+ self._describe_node(f, i, current, action)
+ f.write('}\n')
+
+ def _describe_node(self, f, i, current, action):
+ styles = {
+ 'done': ('rectangle', '#ffffff'),
+ 'building': ('ellipse', '#00ff00'),
+ 'blocked': ('ellipse', '#bbbbbb'),
+ }
+
+ if current is None:
+ shape, color = styles['done']
+ elif i < current:
+ shape, color = styles['done']
+ elif i == current:
+ shape, color = styles['building']
+ elif i > current:
+ shape, color = styles['blocked']
+
+ tmpl = 'a{} [label="{}" shape={} style=filled fillcolor="{}"]\n'
+ f.write(tmpl.format(i, self._describe_action(action), shape, color))
+
+ def _describe_action(self, action):
+ for key in ['action', 'archive']:
+ if key in action:
+ return '{}: {}'.format(key, action[key])
+ for key in ['debootstrap', 'shell', 'python']:
+ if key in action:
+ return key
+ return str(action)
+
def cmd_make_it_so(self, argv):
obj = self._read_object()
- token = self._new_token()
api = self._new_api()
+ token = self._new_token(api)
api.set_token(token)
self._create_resources(api, '/projects', obj.get('projects', []))
@@ -140,9 +243,16 @@ class Icktool(cliapp.Application):
for obj in objs:
api.create(path, obj)
+ def cmd_trigger(self, args):
+ project = args[0]
+ api = self._new_api()
+ token = self._new_token(api)
+ api.set_token(token)
+ self._prettyson(api.trigger(project))
+
def cmd_show(self, args):
- token = self._new_token()
api = self._new_api()
+ token = self._new_token(api)
api.set_token(token)
if not args:
args = [
@@ -160,8 +270,8 @@ class Icktool(cliapp.Application):
api.set_controller_url(self.settings['controller'])
return api
- def _new_auth(self):
- url = self.settings['auth-url']
+ def _new_auth(self, api):
+ url = api.get_auth_url()
client_id, client_secret = self._get_client_creds(url)
ac = ick2.AuthClient()
@@ -169,10 +279,10 @@ class Icktool(cliapp.Application):
ac.set_client_creds(client_id, client_secret)
return ac
- def _new_token(self):
+ def _new_token(self, api):
if self.settings['token']:
return self.settings['token']
- ac = self._new_auth()
+ ac = self._new_auth(api)
scopes = ' '.join(self.settings['scope'])
return ac.get_token(scopes)
@@ -188,19 +298,58 @@ class Icktool(cliapp.Application):
self.output.write('\n')
-class Command:
-
- def __init__(self, api):
- self._api = api
-
- def execute(self):
- raise NotImplementedError()
+class Table:
+
+ def __init__(self):
+ self._column_names = None
+ self._rows = []
+
+ def set_columns(self, *column_names):
+ self._column_names = column_names
+
+ def append_row(self, **kwargs):
+ self._rows.append(kwargs)
+
+ def format(self):
+ assert self._column_names is not None
+ headings = {
+ key: key
+ for key in self._column_names
+ }
+ self._rows.insert(0, headings)
+ widths = self._column_widths()
+ underlines = {
+ key: '-' * widths[key]
+ for key in self._column_names
+ }
+ self._rows.insert(1, underlines)
+ lines = [self._format_row(widths, row) for row in self._rows]
+ return ''.join('{}\n'.format(line) for line in lines)
+
+ def _format_headings(self, widths):
+ row = {
+ key: key
+ for key in self._column_names
+ }
+ return self._format_row(widths, row)
+
+ def _format_row(self, widths, row):
+ return ' | '.join(
+ self._format_cell(widths[x], row[x])
+ for x in self._column_names
+ )
+ def _format_cell(self, width, value):
+ return '%*s' % (width, value)
-class VersionCommand(Command):
+ def _column_widths(self):
+ return {
+ key: self._width(key)
+ for key in self._column_names
+ }
- def execute(self):
- self._api.get_version()
+ def _width(self, column_name):
+ return max(len(str(row[column_name])) for row in self._rows)
Icktool(version=ick2.__version__).run()