GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — plexxi-v2.2.1 ( 2317c4 )
by
unknown
05:42
created

HTTPClient.run()   C

Complexity

Conditions 7

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
c 0
b 0
f 0
dl 0
loc 50
rs 5.5
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 ast
17
import copy
18
import json
19
import uuid
20
21
import requests
22
from requests.auth import HTTPBasicAuth
23
from oslo_config import cfg
24
25
from st2common.runners.base import ActionRunner
26
from st2common import __version__ as st2_version
27
from st2common import log as logging
28
from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED
29
from st2common.constants.action import LIVEACTION_STATUS_FAILED
30
from st2common.constants.action import LIVEACTION_STATUS_TIMED_OUT
31
32
LOG = logging.getLogger(__name__)
33
SUCCESS_STATUS_CODES = [code for code in range(200, 207)]
34
35
# Lookup constants for runner params
36
RUNNER_ON_BEHALF_USER = 'user'
37
RUNNER_URL = 'url'
38
RUNNER_HEADERS = 'headers'  # Debatable whether this should be action params.
39
RUNNER_COOKIES = 'cookies'
40
RUNNER_ALLOW_REDIRECTS = 'allow_redirects'
41
RUNNER_HTTP_PROXY = 'http_proxy'
42
RUNNER_HTTPS_PROXY = 'https_proxy'
43
RUNNER_VERIFY_SSL_CERT = 'verify_ssl_cert'
44
RUNNER_USERNAME = 'username'
45
RUNNER_PASSWORD = 'password'
46
47
# Lookup constants for action params
48
ACTION_AUTH = 'auth'
49
ACTION_BODY = 'body'
50
ACTION_TIMEOUT = 'timeout'
51
ACTION_METHOD = 'method'
52
ACTION_QUERY_PARAMS = 'params'
53
FILE_NAME = 'file_name'
54
FILE_CONTENT = 'file_content'
55
FILE_CONTENT_TYPE = 'file_content_type'
56
57
RESPONSE_BODY_PARSE_FUNCTIONS = {
58
    'application/json': json.loads
59
}
60
61
62
def get_runner():
63
    return HttpRunner(str(uuid.uuid4()))
64
65
66
class HttpRunner(ActionRunner):
67
    def __init__(self, runner_id):
68
        super(HttpRunner, self).__init__(runner_id=runner_id)
69
        self._on_behalf_user = cfg.CONF.system_user.user
70
        self._timeout = 60
71
72
    def pre_run(self):
73
        super(HttpRunner, self).pre_run()
74
75
        LOG.debug('Entering HttpRunner.pre_run() for liveaction_id="%s"', self.liveaction_id)
76
        self._on_behalf_user = self.runner_parameters.get(RUNNER_ON_BEHALF_USER,
77
                                                          self._on_behalf_user)
78
        self._url = self.runner_parameters.get(RUNNER_URL, None)
79
        self._headers = self.runner_parameters.get(RUNNER_HEADERS, {})
80
81
        self._cookies = self.runner_parameters.get(RUNNER_COOKIES, None)
82
        self._allow_redirects = self.runner_parameters.get(RUNNER_ALLOW_REDIRECTS, False)
83
        self._username = self.runner_parameters.get(RUNNER_USERNAME, None)
84
        self._password = self.runner_parameters.get(RUNNER_PASSWORD, None)
85
        self._http_proxy = self.runner_parameters.get(RUNNER_HTTP_PROXY, None)
86
        self._https_proxy = self.runner_parameters.get(RUNNER_HTTPS_PROXY, None)
87
        self._verify_ssl_cert = self.runner_parameters.get(RUNNER_VERIFY_SSL_CERT, None)
88
89
    def run(self, action_parameters):
90
        client = self._get_http_client(action_parameters)
91
92
        try:
93
            result = client.run()
94
        except requests.exceptions.Timeout as e:
95
            result = {'error': str(e)}
96
            status = LIVEACTION_STATUS_TIMED_OUT
97
        else:
98
            status = HttpRunner._get_result_status(result.get('status_code', None))
99
100
        return (status, result, None)
101
102
    def _get_http_client(self, action_parameters):
103
        body = action_parameters.get(ACTION_BODY, None)
104
        timeout = float(action_parameters.get(ACTION_TIMEOUT, self._timeout))
105
        method = action_parameters.get(ACTION_METHOD, None)
106
        params = action_parameters.get(ACTION_QUERY_PARAMS, None)
107
        auth = action_parameters.get(ACTION_AUTH, {})
108
109
        file_name = action_parameters.get(FILE_NAME, None)
110
        file_content = action_parameters.get(FILE_CONTENT, None)
111
        file_content_type = action_parameters.get(FILE_CONTENT_TYPE, None)
112
113
        # Include our user agent and action name so requests can be tracked back
114
        headers = copy.deepcopy(self._headers) if self._headers else {}
115
        headers['User-Agent'] = 'st2/v%s' % (st2_version)
116
        headers['X-Stanley-Action'] = self.action_name
117
118
        if file_name and file_content:
119
            files = {}
120
121
            if file_content_type:
122
                value = (file_content, file_content_type)
123
            else:
124
                value = (file_content)
125
126
            files[file_name] = value
127
        else:
128
            files = None
129
130
        proxies = {}
131
132
        if self._http_proxy:
133
            proxies['http'] = self._http_proxy
134
135
        if self._https_proxy:
136
            proxies['https'] = self._https_proxy
137
138
        return HTTPClient(url=self._url, method=method, body=body, params=params,
139
                          headers=headers, cookies=self._cookies, auth=auth,
140
                          timeout=timeout, allow_redirects=self._allow_redirects,
141
                          proxies=proxies, files=files, verify=self._verify_ssl_cert,
142
                          username=self._username, password=self._password)
143
144
    @staticmethod
145
    def _get_result_status(status_code):
146
        return LIVEACTION_STATUS_SUCCEEDED if status_code in SUCCESS_STATUS_CODES \
147
            else LIVEACTION_STATUS_FAILED
148
149
150
class HTTPClient(object):
151
    def __init__(self, url=None, method=None, body='', params=None, headers=None, cookies=None,
152
                 auth=None, timeout=60, allow_redirects=False, proxies=None,
153
                 files=None, verify=False, username=None, password=None):
154
        if url is None:
155
            raise Exception('URL must be specified.')
156
157
        if method is None:
158
            if files or body:
159
                method = 'POST'
160
            else:
161
                method = 'GET'
162
163
        headers = headers or {}
164
        normalized_headers = self._normalize_headers(headers=headers)
165
        if body and 'content-length' not in normalized_headers:
166
            headers['Content-Length'] = str(len(body))
167
168
        self.url = url
169
        self.method = method
170
        self.headers = headers
171
        self.body = body
172
        self.params = params
173
        self.headers = headers
174
        self.cookies = cookies
175
        self.auth = auth
176
        self.timeout = timeout
177
        self.allow_redirects = allow_redirects
178
        self.proxies = proxies
179
        self.files = files
180
        self.verify = verify
181
        self.username = username
182
        self.password = password
183
184
    def run(self):
185
        results = {}
186
        resp = None
187
        json_content = self._is_json_content()
188
189
        try:
190
            if json_content:
191
                # cast params (body) to dict
192
                data = self._cast_object(self.body)
193
194
                try:
195
                    data = json.dumps(data)
196
                except ValueError:
197
                    msg = 'Request body (%s) can\'t be parsed as JSON' % (data)
198
                    raise ValueError(msg)
199
            else:
200
                data = self.body
201
202
            if self.username or self.password:
203
                self.auth = HTTPBasicAuth(self.username, self.password)
204
205
            resp = requests.request(
206
                self.method,
207
                self.url,
208
                params=self.params,
209
                data=data,
210
                headers=self.headers,
211
                cookies=self.cookies,
212
                auth=self.auth,
213
                timeout=self.timeout,
214
                allow_redirects=self.allow_redirects,
215
                proxies=self.proxies,
216
                files=self.files,
217
                verify=self.verify
218
            )
219
220
            headers = dict(resp.headers)
221
            body, parsed = self._parse_response_body(headers=headers, body=resp.text)
222
223
            results['status_code'] = resp.status_code
224
            results['body'] = body
225
            results['parsed'] = parsed  # flag which indicates if body has been parsed
226
            results['headers'] = headers
227
            return results
228
        except Exception as e:
229
            LOG.exception('Exception making request to remote URL: %s, %s', self.url, e)
230
            raise
231
        finally:
232
            if resp:
233
                resp.close()
234
235
    def _parse_response_body(self, headers, body):
236
        """
237
        :param body: Response body.
238
        :type body: ``str``
239
240
        :return: (parsed body, flag which indicates if body has been parsed)
241
        :rtype: (``object``, ``bool``)
242
        """
243
        body = body or ''
244
        headers = self._normalize_headers(headers=headers)
245
        content_type = headers.get('content-type', None)
246
        parsed = False
247
248
        if not content_type:
249
            return (body, parsed)
250
251
        # The header can also contain charset which we simply discard
252
        content_type = content_type.split(';')[0]
253
        parse_func = RESPONSE_BODY_PARSE_FUNCTIONS.get(content_type, None)
254
255
        if not parse_func:
256
            return (body, parsed)
257
258
        LOG.debug('Parsing body with content type: %s', content_type)
259
260
        try:
261
            body = parse_func(body)
262
        except Exception:
263
            LOG.exception('Failed to parse body')
264
        else:
265
            parsed = True
266
267
        return (body, parsed)
268
269
    def _normalize_headers(self, headers):
270
        """
271
        Normalize the header keys by lowercasing all the keys.
272
        """
273
        result = {}
274
        for key, value in headers.items():
275
            result[key.lower()] = value
276
277
        return result
278
279
    def _is_json_content(self):
280
        normalized = self._normalize_headers(self.headers)
281
        return normalized.get('content-type', None) == 'application/json'
282
283
    def _cast_object(self, value):
284
        if isinstance(value, str) or isinstance(value, unicode):
285
            try:
286
                return json.loads(value)
287
            except:
288
                return ast.literal_eval(value)
289
        else:
290
            return value
291