summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2018-07-29 11:39:05 +0300
committerLars Wirzenius <liw@liw.fi>2018-07-29 11:39:05 +0300
commitbf66974141b86bbdcc8c1036bcacb6be3c60933a (patch)
tree0fc3de160c5ea32c03b25839357d152ec2bdeb88
downloadicklint-bf66974141b86bbdcc8c1036bcacb6be3c60933a.tar.gz
Initial commit
-rw-r--r--README11
-rwxr-xr-xicklint172
2 files changed, 183 insertions, 0 deletions
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)