#!/usr/bin/env python3 # Copyright (C) 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 # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . 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)