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 — develop-v1.3.1 ( 969842...8fa207 )
by
unknown
05:56
created

PythonRunner.run()   F

Complexity

Conditions 12

Size

Total Lines 78

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 12
dl 0
loc 78
rs 2.0916

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like PythonRunner.run() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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 st2common.util.green.shell import run_command
27
from st2common import log as logging
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.error_messages import PACK_VIRTUALENV_DOESNT_EXIST
33
from st2common.util.sandboxing import get_sandbox_path
34
from st2common.util.sandboxing import get_sandbox_python_path
35
from st2common.util.sandboxing import get_sandbox_python_binary_path
36
from st2common.util.sandboxing import get_sandbox_virtualenv_path
37
from st2common.constants.runners import PYTHON_RUNNER_DEFAULT_ACTION_TIMEOUT
38
39
__all__ = [
40
    'get_runner',
41
42
    'PythonRunner',
43
    'Action'
44
]
45
46
LOG = logging.getLogger(__name__)
47
48
# constants to lookup in runner_parameters.
49
RUNNER_ENV = 'env'
50
RUNNER_TIMEOUT = 'timeout'
51
52
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
53
WRAPPER_SCRIPT_NAME = 'python_action_wrapper.py'
54
WRAPPER_SCRIPT_PATH = os.path.join(BASE_DIR, WRAPPER_SCRIPT_NAME)
55
56
57
def get_runner():
58
    return PythonRunner(str(uuid.uuid4()))
59
60
61
@six.add_metaclass(abc.ABCMeta)
62
class Action(object):
63
    """
64
    Base action class other Python actions should inherit from.
65
    """
66
67
    description = None
68
69
    def __init__(self, config=None):
70
        """
71
        :param config: Action config.
72
        :type config: ``dict``
73
        """
74
        self.config = config or {}
75
        # logger and datastore are assigned in PythonActionWrapper._get_action_instance
76
        self.logger = None
77
        self.datastore = None
78
79
    @abc.abstractmethod
80
    def run(self, **kwargs):
81
        pass
82
83
84
class PythonRunner(ActionRunner):
85
86
    def __init__(self, runner_id, timeout=PYTHON_RUNNER_DEFAULT_ACTION_TIMEOUT):
87
        """
88
        :param timeout: Action execution timeout in seconds.
89
        :type timeout: ``int``
90
        """
91
        super(PythonRunner, self).__init__(runner_id=runner_id)
92
        self._timeout = timeout
93
94
    def pre_run(self):
95
        # TODO :This is awful, but the way "runner_parameters" and other variables get
96
        # assigned on the runner instance is even worse. Those arguments should
97
        # be passed to the constructor.
98
        self._env = self.runner_parameters.get(RUNNER_ENV, {})
99
        self._timeout = self.runner_parameters.get(RUNNER_TIMEOUT, self._timeout)
100
101
    def run(self, action_parameters):
102
        pack = self.get_pack_name()
103
        serialized_parameters = json.dumps(action_parameters) if action_parameters else ''
104
        virtualenv_path = get_sandbox_virtualenv_path(pack=pack)
105
        python_path = get_sandbox_python_binary_path(pack=pack)
106
107
        if virtualenv_path and not os.path.isdir(virtualenv_path):
108
            format_values = {'pack': pack, 'virtualenv_path': virtualenv_path}
109
            msg = PACK_VIRTUALENV_DOESNT_EXIST % format_values
110
            raise Exception(msg)
111
112
        if not self.entry_point:
113
            raise Exception('Action "%s" is missing entry_point attribute' % (self.action.name))
114
115
        args = [
116
            python_path,
117
            WRAPPER_SCRIPT_PATH,
118
            '--pack=%s' % (pack),
119
            '--file-path=%s' % (self.entry_point),
120
            '--parameters=%s' % (serialized_parameters),
121
            '--parent-args=%s' % (json.dumps(sys.argv[1:]))
122
        ]
123
124
        # We need to ensure all the st2 dependencies are also available to the
125
        # subprocess
126
        env = os.environ.copy()
127
        env['PATH'] = get_sandbox_path(virtualenv_path=virtualenv_path)
128
        env['PYTHONPATH'] = get_sandbox_python_path(inherit_from_parent=True,
129
                                                    inherit_parent_virtualenv=True)
130
131
        # Include user provided environment variables (if any)
132
        user_env_vars = self._get_env_vars()
133
        env.update(user_env_vars)
134
135
        # Include common st2 environment variables
136
        st2_env_vars = self._get_common_action_env_variables()
137
        env.update(st2_env_vars)
138
139
        exit_code, stdout, stderr, timed_out = run_command(cmd=args, stdout=subprocess.PIPE,
140
                                                           stderr=subprocess.PIPE, shell=False,
141
                                                           env=env, timeout=self._timeout)
142
143
        if timed_out:
144
            error = 'Action failed to complete in %s seconds' % (self._timeout)
145
        else:
146
            error = None
147
148
        if ACTION_OUTPUT_RESULT_DELIMITER in stdout:
149
            split = stdout.split(ACTION_OUTPUT_RESULT_DELIMITER)
150
            assert len(split) == 3
151
            result = split[1].strip()
152
            stdout = split[0] + split[2]
153
        else:
154
            result = None
155
156
        try:
157
            result = json.loads(result)
158
        except:
159
            pass
160
161
        output = {
162
            'stdout': stdout,
163
            'stderr': stderr,
164
            'exit_code': exit_code,
165
            'result': result
166
        }
167
168
        if error:
169
            output['error'] = error
170
171
        if exit_code == 0:
172
            status = LIVEACTION_STATUS_SUCCEEDED
173
        elif timed_out:
174
            status = LIVEACTION_STATUS_TIMED_OUT
175
        else:
176
            status = LIVEACTION_STATUS_FAILED
177
178
        return (status, output, None)
179
180
    def _get_env_vars(self):
181
        """
182
        Return sanitized environment variables which will be used when launching
183
        a subprocess.
184
185
        :rtype: ``dict``
186
        """
187
        # Don't allow user to override PYTHONPATH since this would break things
188
        blacklisted_vars = ['pythonpath']
189
        env_vars = {}
190
191
        if self._env:
192
            env_vars.update(self._env)
193
194
        # Remove "blacklisted" environment variables
195
        to_delete = []
196
        for key, value in env_vars.items():
197
            if key.lower() in blacklisted_vars:
198
                to_delete.append(key)
199
200
        for key in to_delete:
201
            del env_vars[key]
202
203
        return env_vars
204