summaryrefslogtreecommitdiff
path: root/oslosphinx/check_blueprints.py
blob: e241edc16f26f641fff032056d6f012fd2ce0140 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# 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')