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 — pip_8.1.2 ( 36f804 )
by
unknown
06:44
created

PythonRunner._get_final_status()   D

Complexity

Conditions 8

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
c 0
b 0
f 0
dl 0
loc 26
rs 4
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 os
17
import sys
18
import abc
19
import json
20
import uuid
21
22
import six
23
from eventlet.green import subprocess
24
25
from st2actions.runners import ActionRunner
26
from st2actions.runners.utils import get_logger_for_python_runner_action
27
from st2common.util.green.shell import run_command
28
from st2common.constants.action import ACTION_OUTPUT_RESULT_DELIMITER
29
from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED
30
from st2common.constants.action import LIVEACTION_STATUS_FAILED
31
from st2common.constants.action import LIVEACTION_STATUS_TIMED_OUT
32
from st2common.constants.runners import PYTHON_RUNNER_INVALID_ACTION_STATUS_EXIT_CODE
33
from st2common.constants.error_messages import PACK_VIRTUALENV_DOESNT_EXIST
34
from st2common.constants.runners import PYTHON_RUNNER_DEFAULT_ACTION_TIMEOUT
35
from st2common.constants.system import API_URL_ENV_VARIABLE_NAME
36
from st2common.constants.system import AUTH_TOKEN_ENV_VARIABLE_NAME
37
from st2common.util.api import get_full_public_api_url
38
from st2common.util.sandboxing import get_sandbox_path
39
from st2common.util.sandboxing import get_sandbox_python_path
40
from st2common.util.sandboxing import get_sandbox_python_binary_path
41
from st2common.util.sandboxing import get_sandbox_virtualenv_path
42
43
44
__all__ = [
45
    'get_runner',
46
47
    'PythonRunner',
48
    'Action'
49
]
50
51
# constants to lookup in runner_parameters.
52
RUNNER_ENV = 'env'
53
RUNNER_TIMEOUT = 'timeout'
54
55
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
56
WRAPPER_SCRIPT_NAME = 'python_action_wrapper.py'
57
WRAPPER_SCRIPT_PATH = os.path.join(BASE_DIR, WRAPPER_SCRIPT_NAME)
58
59
60
def get_runner():
61
    return PythonRunner(str(uuid.uuid4()))
62
63
64
@six.add_metaclass(abc.ABCMeta)
65
class Action(object):
66
    """
67
    Base action class other Python actions should inherit from.
68
    """
69
70
    description = None
71
72
    def __init__(self, config=None, action_service=None):
73
        """
74
        :param config: Action config.
75
        :type config: ``dict``
76
77
        :param action_service: ActionService object.
78
        :type action_service: :class:`ActionService~
79
        """
80
        self.config = config or {}
81
        self.action_service = action_service
82
        self.logger = get_logger_for_python_runner_action(action_name=self.__class__.__name__)
83
84
    @abc.abstractmethod
85
    def run(self, **kwargs):
86
        pass
87
88
89
class PythonRunner(ActionRunner):
90
91
    def __init__(self, runner_id, timeout=PYTHON_RUNNER_DEFAULT_ACTION_TIMEOUT):
92
        """
93
        :param timeout: Action execution timeout in seconds.
94
        :type timeout: ``int``
95
        """
96
        super(PythonRunner, self).__init__(runner_id=runner_id)
97
        self._timeout = timeout
98
99
    def pre_run(self):
100
        super(PythonRunner, self).pre_run()
101
102
        # TODO :This is awful, but the way "runner_parameters" and other variables get
103
        # assigned on the runner instance is even worse. Those arguments should
104
        # be passed to the constructor.
105
        self._env = self.runner_parameters.get(RUNNER_ENV, {})
106
        self._timeout = self.runner_parameters.get(RUNNER_TIMEOUT, self._timeout)
107
108
    def run(self, action_parameters):
109
        pack = self.get_pack_name()
110
        user = self.get_user()
111
        serialized_parameters = json.dumps(action_parameters) if action_parameters else ''
112
        virtualenv_path = get_sandbox_virtualenv_path(pack=pack)
113
        python_path = get_sandbox_python_binary_path(pack=pack)
114
115
        if virtualenv_path and not os.path.isdir(virtualenv_path):
116
            format_values = {'pack': pack, 'virtualenv_path': virtualenv_path}
117
            msg = PACK_VIRTUALENV_DOESNT_EXIST % format_values
118
            raise Exception(msg)
119
120
        if not self.entry_point:
121
            raise Exception('Action "%s" is missing entry_point attribute' % (self.action.name))
122
123
        args = [
124
            python_path,
125
            WRAPPER_SCRIPT_PATH,
126
            '--pack=%s' % (pack),
127
            '--file-path=%s' % (self.entry_point),
128
            '--parameters=%s' % (serialized_parameters),
129
            '--user=%s' % (user),
130
            '--parent-args=%s' % (json.dumps(sys.argv[1:]))
131
        ]
132
133
        # We need to ensure all the st2 dependencies are also available to the
134
        # subprocess
135
        env = os.environ.copy()
136
        env['PATH'] = get_sandbox_path(virtualenv_path=virtualenv_path)
137
        env['PYTHONPATH'] = get_sandbox_python_path(inherit_from_parent=True,
138
                                                    inherit_parent_virtualenv=True)
139
140
        # Include user provided environment variables (if any)
141
        user_env_vars = self._get_env_vars()
142
        env.update(user_env_vars)
143
144
        # Include common st2 environment variables
145
        st2_env_vars = self._get_common_action_env_variables()
146
        env.update(st2_env_vars)
147
        datastore_env_vars = self._get_datastore_access_env_vars()
148
        env.update(datastore_env_vars)
149
150
        exit_code, stdout, stderr, timed_out = run_command(cmd=args, stdout=subprocess.PIPE,
151
                                                           stderr=subprocess.PIPE, shell=False,
152
                                                           env=env, timeout=self._timeout)
153
154
        return self._get_output_values(exit_code, stdout, stderr, timed_out)
155
156
    def _get_output_values(self, exit_code, stdout, stderr, timed_out):
157
        """
158
        Return sanitized output values.
159
160
        :return: Tuple with status, output and None
161
162
        :rtype: ``tuple``
163
        """
164
        if timed_out:
165
            error = 'Action failed to complete in %s seconds' % (self._timeout)
166
        else:
167
            error = None
168
169
        if exit_code == PYTHON_RUNNER_INVALID_ACTION_STATUS_EXIT_CODE:
170
            # TODO: Mark as failed instead
171
            raise ValueError(stderr)
172
173
        if ACTION_OUTPUT_RESULT_DELIMITER in stdout:
174
            split = stdout.split(ACTION_OUTPUT_RESULT_DELIMITER)
175
            assert len(split) == 3
176
            action_result = split[1].strip()
177
            stdout = split[0] + split[2]
178
        else:
179
            action_result = None
180
181
        # Parse the serialized action result object
182
        try:
183
            action_result = json.loads(action_result)
184
        except:
185
            pass
186
187
        if action_result and isinstance(action_result, dict):
188
            result = action_result.get('result', None)
189
            status = action_result.get('status', None)
190
        else:
191
            result = 'None'
192
            status = None
193
194
        output = {
195
            'stdout': stdout,
196
            'stderr': stderr,
197
            'exit_code': exit_code,
198
            'result': result
199
        }
200
201
        if error:
202
            output['error'] = error
203
204
        status = self._get_final_status(action_status=status, timed_out=timed_out,
205
                                        exit_code=exit_code)
206
        return (status, output, None)
207
208
    def _get_final_status(self, action_status, timed_out, exit_code):
209
        """
210
        Return final status based on action's status, time out value and
211
        exit code. Example: succeeded, failed, timeout.
212
213
        :return: status
214
215
        :rtype: ``str``
216
        """
217
        if action_status is not None:
218
            if exit_code == 0 and action_status is True:
219
                status = LIVEACTION_STATUS_SUCCEEDED
220
            elif exit_code == 0 and action_status is False:
221
                status = LIVEACTION_STATUS_FAILED
222
            else:
223
                status = LIVEACTION_STATUS_FAILED
224
        else:
225
            if exit_code == 0:
226
                status = LIVEACTION_STATUS_SUCCEEDED
227
            else:
228
                status = LIVEACTION_STATUS_FAILED
229
230
        if timed_out:
231
            status = LIVEACTION_STATUS_TIMED_OUT
232
233
        return status
234
235
    def _get_env_vars(self):
236
        """
237
        Return sanitized environment variables which will be used when launching
238
        a subprocess.
239
240
        :rtype: ``dict``
241
        """
242
        # Don't allow user to override PYTHONPATH since this would break things
243
        blacklisted_vars = ['pythonpath']
244
        env_vars = {}
245
246
        if self._env:
247
            env_vars.update(self._env)
248
249
        # Remove "blacklisted" environment variables
250
        to_delete = []
251
        for key, value in env_vars.items():
252
            if key.lower() in blacklisted_vars:
253
                to_delete.append(key)
254
255
        for key in to_delete:
256
            del env_vars[key]
257
258
        return env_vars
259
260
    def _get_datastore_access_env_vars(self):
261
        """
262
        Return environment variables so datastore access using client (from st2client)
263
        is possible with actions. This is done to be compatible with sensors.
264
265
        :rtype: ``dict``
266
        """
267
        env_vars = {}
268
        if self.auth_token:
269
            env_vars[AUTH_TOKEN_ENV_VARIABLE_NAME] = self.auth_token.token
270
        env_vars[API_URL_ENV_VARIABLE_NAME] = get_full_public_api_url()
271
272
        return env_vars
273