From bf66974141b86bbdcc8c1036bcacb6be3c60933a Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 29 Jul 2018 11:39:05 +0300 Subject: Initial commit --- README | 11 +++++ icklint | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 README create mode 100755 icklint diff --git a/README b/README new file mode 100644 index 0000000..00cb79b --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +icklint +============================================================================= + +icklint checks for problems ("lint") in ick project and pipeline +definitions. + +Use icktool to get project and pipeline definitions from the +controller: + + icktool show /projects > projects.json + icktool show /pipelines > pipelines.json diff --git a/icklint b/icklint new file mode 100755 index 0000000..c24dde6 --- /dev/null +++ b/icklint @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + + +import itertools +import sys + + +import yaml + + +# Define individual checks as functions. A check can check a pipeline +# (name starts with "pipeline_"), a project, or a pair of project and +# one of the pipelines it uses. +# +# Pipeline checks get the pipeline object as the argument, where +# object is the dict from the pipeline spec. +# +# Project checks get the project and a dict-like object with all +# pipelines. +# +# Pair checks get the project and pipeline objects. +# +# Each check should yield all the error messages. An error message can +# use "{project}" and "{pipeline}" to refer to the name of the project +# and pipeline in question. + + +def pipeline_has_actions(pl): + if not pl.get('actions'): + yield '{pipeline}: does not have actions' + + +def pipeline_parameters_spelling(pl): + if 'parameter' in pl: + yield '{pipeline}: has parameter field (note singular)' + + +def project_has_pipelines(pr, pipelines): + if not pr.get('pipelines'): + yield "{project}: doesn't have pipelines" + + +def project_has_parameters(pr, pipelines): + if not pr.get('parameters'): + yield "{project}: doesn't have pipelines" + + +def project_params_wanted(pr, pipelines): + wanted = [] + for plname in pr.get('pipelines', []): + pl = pipelines.get(plname) + if pl is not None: + wanted.extend(pl.get('parameters', [])) + + params = pr.get('parameters', {}) + for param in params: + if param not in wanted: + yield '{project}: %s defined, but not wanted' % param + + +def pair_pipeline_exists(pr, pl): + if pl is None: + yield '{project}: {pipeline}: pipeline does not exist' + + +def pair_params_defined(pr, pl): + params = pr.get('parameters', {}) + for wanted in pl.get('parameters', []): + if wanted not in params: + yield '{project}: {pipeline}: %s wanted, but not defined' % wanted + + +# The rest if icklint infrastructure. + + +class Linter: + + def __init__(self, projects, pipelines): + self._projects = projects + self._pipelines = pipelines + + def check(self): + checks = [ + self.check_pipelines, + self.check_projects, + self.check_pairs, + ] + + for check in checks: + for msg in check(): + ok = False + yield msg + + def find_checkers(self, prefix): + g = globals() + return [ + g[name] + for name in g + if name.startswith(prefix + '_') + ] + + def check_pipelines(self): + checkers = self.find_checkers('pipeline') + for checker in checkers: + for plname, pl in self._pipelines.items(): + for msg in checker(pl): + yield msg.format(pipeline=plname) + + def check_projects(self): + checkers = self.find_checkers('project') + for checker in checkers: + for prname, pr in self._projects.items(): + for msg in checker(pr, self._pipelines): + yield msg.format(project=prname) + + def check_pairs(self): + checkers = self.find_checkers('pair') + for checker in checkers: + for prname, pr in self._projects.items(): + for plname in pr.get('pipelines', []): + pl = self._pipelines.get(plname) + for msg in checker(pr, pl): + yield msg.format(project=prname, pipeline=plname) + + +class Things: + + def __init__(self, items_field, name_field): + self._items_field = items_field + self._name_field = name_field + self._dict = {} + + def from_file(self, filename): + with open(filename) as f: + items = yaml.safe_load(f) + + for item in items[self._items_field]: + name = item[self._name_field] + self[name] = item + + def __contains__(self, name): + return name in self._dict + + def __getitem__(self, name): + return self._dict[name] + + def __setitem__(self, name, value): + self._dict[name] = value + + def get(self, name, default=None): + return self._dict.get(name, default) + + def keys(self): + return list(sorted(self._dict.keys())) + + def items(self): + return self._dict.items() + + +projects = Things('projects', 'project') +projects.from_file(sys.argv[1]) + +pipelines = Things('pipelines', 'pipeline') +pipelines.from_file(sys.argv[2]) + +linter = Linter(projects, pipelines) +ok = True +for msg in linter.check(): + ok = False + print(msg) +if not ok: + sys.exit(1) -- cgit v1.2.1