Completed
Pull Request — master (#2842)
by Edward
05:40
created

_fetch_and_compile_index()   D

Complexity

Conditions 8

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 8
c 3
b 0
f 0
dl 0
loc 49
rs 4.7619
1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import itertools
17
import json
18
19
import requests
20
import six
21
from oslo_config import cfg
22
23
from st2common import log as logging
24
from st2common.persistence.pack import Pack
25
26
__all__ = [
27
    'get_pack_by_ref',
28
    'fetch_pack_index',
29
    'get_pack_from_index',
30
    'search_pack_index'
31
]
32
33
EXCLUDE_FIELDS = [
34
    "repo_url",
35
    "email"
36
]
37
38
SEARCH_PRIORITY = [
39
    "name",
40
    "keywords"
41
]
42
43
LOG = logging.getLogger(__name__)
44
45
46
def _build_index_list(index_url):
47
    if not index_url:
48
        # Reversing the indexes list from config so that the indexes have
49
        # descending (left-to-right) priority.
50
        # When multiple indexes have a pack with a given name, the index
51
        # that comes first in the list will be used.
52
        index_urls = cfg.CONF.content.index_url[::-1]
53
    elif isinstance(index_url, str):
54
        index_urls = [index_url]
55
    elif hasattr(index_url, '__iter__'):
56
        index_urls = index_url
57
    else:
58
        raise TypeError('"index_url" should either be a string or an iterable object.')
59
    return index_urls
60
61
62
def _fetch_and_compile_index(index_urls, logger=None):
63
    """
64
    Go through the index list and compile results into a single object.
65
    """
66
    status = []
67
    index = {}
68
69
    for index_url in index_urls:
70
        index_status = {
71
            'url': index_url,
72
            'packs': 0,
73
            'message': None,
74
            'error': None,
75
        }
76
        index_json = None
77
78
        try:
79
            request = requests.get(index_url)
80
            request.raise_for_status()
81
            index_json = request.json()
82
        except ValueError as e:
83
            index_status['error'] = 'malformed'
84
            index_status['message'] = repr(e)
85
        except requests.exceptions.RequestException as e:
86
            index_status['error'] = 'unresponsive'
87
            index_status['message'] = repr(e)
88
89
        if index_json == {}:
90
            index_status['error'] = 'empty'
91
            index_status['message'] = 'The index URL returned an empty object.'
92
        elif type(index_json) is list:
93
            index_status['error'] = 'malformed'
94
            index_status['message'] = 'Expected an index object, got a list instead.'
95
        elif 'packs' not in index_json:
96
            index_status['error'] = 'malformed'
97
            index_status['message'] = 'Index object is missing "packs" attribute.'
98
99
        if index_status['error']:
100
            logger.error("Index parsing error: %s" % json.dumps(index_status, indent=4))
101
        else:
102
            # TODO: Notify on a duplicate pack aka pack being overwritten from a different index
103
            packs_data = index_json['packs']
104
            index_status['message'] = 'Success.'
105
            index_status['packs'] = len(packs_data)
106
            index.update(packs_data)
107
108
        status.append(index_status)
109
110
    return index, status
111
112
113
def get_pack_by_ref(pack_ref):
114
    """
115
    Retrieve PackDB by the provided reference.
116
    """
117
    pack_db = Pack.get_by_ref(pack_ref)
118
    return pack_db
119
120
121
def fetch_pack_index(index_url=None, logger=None, allow_empty=False):
122
    """
123
    Fetch the pack indexes (either from the config or provided as an argument)
124
    and return the object.
125
    """
126
    logger = logger or LOG
127
128
    index_urls = _build_index_list(index_url)
129
    index, status = _fetch_and_compile_index(index_urls, logger)
130
131
    # If one of the indexes on the list is unresponsive, we do not throw
132
    # immediately. The only case where an exception is raised is when no
133
    # results could be obtained from all listed indexes.
134
    # This behavior allows for mirrors / backups and handling connection
135
    # or network issues in one of the indexes.
136
    if not index and not allow_empty:
137
        raise ValueError("No results from the %s: tried %s.\nStatus: %s" % (
138
            ("index" if len(index_urls) == 1 else "indexes"),
139
            ", ".join(index_urls),
140
            json.dumps(status, indent=4)
141
        ))
142
    return (index, status)
143
144
145
def get_pack_from_index(pack):
146
    """
147
    Search index by pack name.
148
    Returns a pack.
149
    """
150
    if not pack:
151
        raise ValueError("Pack name must be specified.")
152
153
    index, _ = fetch_pack_index()
154
155
    return index.get(pack)
156
157
158
def search_pack_index(query, exclude=None, priority=None):
159
    """
160
    Search the pack index by query.
161
    Returns a list of matches for a query.
162
    """
163
    if not query:
164
        raise ValueError("Query must be specified.")
165
166
    if not exclude:
167
        exclude = EXCLUDE_FIELDS
168
    if not priority:
169
        priority = SEARCH_PRIORITY
170
171
    index, _ = fetch_pack_index()
172
173
    matches = [[] for i in range(len(priority) + 1)]
174
    for pack in six.itervalues(index):
175
176
        for key, value in six.iteritems(pack):
177
            if not hasattr(value, '__contains__'):
178
                value = str(value)
179
180
            if key not in exclude and query in value:
181
                if key in priority:
182
                    matches[priority.index(key)].append(pack)
183
                else:
184
                    matches[-1].append(pack)
185
                break
186
187
    return list(itertools.chain.from_iterable(matches))
188