Codebase list oslo-sphinx / debian/4.18.0-3 oslosphinx / check_blueprints.py
debian/4.18.0-3

Tree @debian/4.18.0-3 (Download .tar.gz)

check_blueprints.py @debian/4.18.0-3raw · history · blame

# All Rights Reserved.
#
#    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 the name of the spec file matches the name of a blueprint.
"""

import requests


class BlueprintChecker(object):

    def __init__(self, app):
        self.app = app
        self.project_names = []
        self._good_bps = set()
        self._prefix = None
        self._warn_search = 'unset'

    BP_URL_TEMPLATE = 'https://api.launchpad.net/devel/%s/+spec/%s'
    PROJ_LIST_URL_TEMPLATE = 'https://api.launchpad.net/1.0/%s/projects'

    def _load_project_settings(self):
        if self.project_names:
            return
        # If a project_name is set in the configuration, use
        # that. Otherwise, allow any project in the project group.
        project_name = self.app.config.check_blueprints_project
        pg_name = self.app.config.check_blueprints_project_group
        if project_name:
            self.project_names = [project_name]
            self._warn_search = 'the %s project' % project_name
        else:
            proj_list_response = requests.get(self.PROJ_LIST_URL_TEMPLATE
                                              % pg_name)
            projects = proj_list_response.json()['entries']
            self.project_names = [p['name'] for p in projects]
            self._warn_search = ('any projects in the %s project group'
                                 % pg_name)

    @property
    def desired_prefix(self):
        """Determine the prefix for files we care to check.

        We only care about blueprints in the current release, if the
        check_blueprints_release option is set.

        """
        if self._prefix is None:
            release = self.app.config.check_blueprints_release
            if release:
                self._prefix = 'specs/%s/' % release
            else:
                self._prefix = 'specs/'
        return self._prefix

    def doctree_resolved(self, app, doctree, docname):
        """Hook registered as event handler."""
        if not docname.startswith(self.desired_prefix):
            return
        bp_name = docname.split('/')[-1]
        if bp_name == 'index':
            return
        self.check(bp_name)

    def blueprint_exists(self, project_name, bp_name):
        """Return boolean indicating whether the blueprint exists."""
        self.app.info('Checking for %s in %s' % (bp_name, project_name))
        url = self.BP_URL_TEMPLATE % (project_name, bp_name)
        response = requests.get(url)
        if response.status_code == 200:
            self.app.info('Found %s in %s' % (bp_name, project_name))
            return True
        return False

    def check(self, bp_name):
        """Given one blueprint name, check to see if it is valid."""
        if bp_name in self._good_bps:
            return True
        self._load_project_settings()
        self.app.info('')  # emit newline
        candidate_project, dash, bp_name_to_find = bp_name.partition('-')
        if candidate_project in self.project_names:
            # First check the shortened name of the blueprint in the project.
            if self.blueprint_exists(candidate_project, bp_name_to_find):
                return
            # Then check the full name of the blueprint in the project.
            if self.blueprint_exists(candidate_project, bp_name):
                return
            self.app.info(
                ('Blueprint name %r looks like it starts with a project '
                 'name, but %r was not found in project %r') %
                (bp_name, bp_name_to_find, candidate_project)
            )
        else:
            self.app.info(
                'Blueprint checking is faster if the file names '
                'start with the launchpad project name.'
            )
        for project_name in self.project_names:
            if self.blueprint_exists(project_name, bp_name):
                self._good_bps.add(bp_name)
                break
        else:
            self.app.warn(
                'Could not find a blueprint called %r in %s'
                % (bp_name, self._warn_search),
                location=(bp_name, 0),
            )
            raise ValueError(
                'Document %s does not match any blueprint name in %s'
                % (bp_name, self._warn_search))


def setup(app):
    app.info('Initializing %s' % __name__)
    checker = BlueprintChecker(app)
    app.connect('doctree-resolved', checker.doctree_resolved)
    app.add_config_value('check_blueprints_project_group', 'openstack', 'env')
    app.add_config_value('check_blueprints_project', '', 'env')
    app.add_config_value('check_blueprints_release', '', 'env')