#!/usr/bin/env python # # Copyright 2019 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Ensure that all jobs and roles appear in the documentation. import os import re import sys import yaml class ZuulSafeLoader(yaml.SafeLoader): def __init__(self, *args, **kwargs): super(ZuulSafeLoader, self).__init__(*args, **kwargs) self.add_multi_constructor('!encrypted/', self.construct_encrypted) @classmethod def construct_encrypted(cls, loader, tag_suffix, node): return loader.construct_sequence(node) class Layout(object): def __init__(self): self.jobs = [] class ZuulConfig(object): def find_zuul_yaml(self): root = os.getcwd() while root: for fn in ['zuul.yaml', '.zuul.yaml', 'zuul.d', '.zuul.d']: path = os.path.join(root, fn) if os.path.exists(path): return path root = os.path.split(root)[0] raise Exception( "Unable to find zuul config in zuul.yaml, .zuul.yaml," " zuul.d or .zuul.d") def parse_zuul_yaml(self, path): with open(path) as f: data = yaml.load(f, Loader=ZuulSafeLoader) layout = Layout() for obj in data: if 'job' in obj: layout.jobs.append(obj['job']) return layout def parse_zuul_d(self, path): layout = Layout() for conf in os.listdir(path): with open(os.path.join(path, conf)) as f: data = yaml.load(f, Loader=ZuulSafeLoader) for obj in data: if 'job' in obj: layout.jobs.append(obj['job']) return layout def parse_zuul_layout(self): path = self.find_zuul_yaml() if path.endswith('zuul.d'): layout = self.parse_zuul_d(path) else: layout = self.parse_zuul_yaml(path) return layout def __init__(self): self.layout = self.parse_zuul_layout() class Docs(object): def __init__(self): self.jobs = set() self.roles = set() self.autojobs = False self.autoroles = False self.walk(os.path.join(os.getcwd(), 'doc', 'source')) def walk(self, path): for root, dirs, files in os.walk(path): for fn in files: if fn.endswith('.rst'): with open(os.path.join(root, fn)) as f: for line in f: m = re.match(r'.*\.\. zuul:job:: (.*)$', line) if m: self.jobs.add(m.group(1)) m = re.match(r'.*\.\. zuul:autojob:: (.*)$', line) if m: self.jobs.add(m.group(1)) m = re.match(r'.*\.\. zuul:autojobs::.*$', line) if m: self.autojobs = True m = re.match(r'.*\.\. zuul:role:: (.*)$', line) if m: self.roles.add(m.group(1)) m = re.match(r'.*\.\. zuul:autorole:: (.*)$', line) if m: self.roles.add(m.group(1)) m = re.match(r'.*\.\. zuul:autoroles::.*$', line) if m: self.autoroles = True class Roles(object): def __init__(self): self.roles = set() self.walk(os.path.join(os.getcwd(), 'roles')) def walk(self, path): for role in os.listdir(path): if os.path.isdir(os.path.join(path, role, 'tasks')): self.roles.add(role) z = ZuulConfig() r = Roles() d = Docs() ret = 0 if not d.autoroles: for role in r.roles: if role not in d.roles: print("Role %s not included in document tree" % (role,)) ret = 1 for job in [x['name'] for x in z.layout.jobs]: if job not in d.jobs: print("Job %s not included in document tree" % (job,)) ret = 1 sys.exit(ret)