Passed
Push — develop ( 51eafa...7602af )
by Plexxi
06:51 queued 03:20
created

PythonActionWrapper.run()   B

Complexity

Conditions 6

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
c 0
b 0
f 0
dl 0
loc 30
rs 7.5384
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 sys
17
import json
18
import argparse
19
import traceback
20
from oslo_config import cfg
21
22
from st2common import log as logging
23
from st2actions import config
24
from st2actions.runners.pythonrunner import Action
25
from st2actions.runners.utils import get_logger_for_python_runner_action
26
from st2actions.runners.utils import get_action_class_instance
27
from st2common.util import loader as action_loader
28
from st2common.util.config_loader import ContentPackConfigLoader
29
from st2common.constants.action import ACTION_OUTPUT_RESULT_DELIMITER
30
from st2common.constants.keyvalue import SYSTEM_SCOPE
31
from st2common.constants.error_codes import PYTHON_ACTION_INVALID_STATUS_EXIT_CODE
32
from st2common.service_setup import db_setup
33
from st2common.services.datastore import DatastoreService
34
from st2common.exceptions.invalidstatus import InvalidStatusException
35
36
__all__ = [
37
    'PythonActionWrapper',
38
    'ActionService'
39
]
40
41
LOG = logging.getLogger(__name__)
42
43
44
class ActionService(object):
45
    """
46
    Instance of this class is passed to the action instance and exposes "public"
47
    methods which can be called by the action.
48
    """
49
50
    def __init__(self, action_wrapper):
51
        logger = get_logger_for_python_runner_action(action_name=action_wrapper._class_name)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _class_name was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
52
53
        self._action_wrapper = action_wrapper
54
        self._datastore_service = DatastoreService(logger=logger,
55
                                                   pack_name=self._action_wrapper._pack,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _pack was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
56
                                                   class_name=self._action_wrapper._class_name,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _class_name was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
57
                                                   api_username='action_service')
58
59
    ##################################
60
    # Methods for datastore management
61
    ##################################
62
63
    def list_values(self, local=True, prefix=None):
64
        return self._datastore_service.list_values(local, prefix)
65
66
    def get_value(self, name, local=True, scope=SYSTEM_SCOPE, decrypt=False):
67
        return self._datastore_service.get_value(name, local, scope=scope, decrypt=decrypt)
68
69
    def set_value(self, name, value, ttl=None, local=True, scope=SYSTEM_SCOPE, encrypt=False):
70
        return self._datastore_service.set_value(name, value, ttl, local, scope=scope,
71
                                                 encrypt=encrypt)
72
73
    def delete_value(self, name, local=True, scope=SYSTEM_SCOPE):
74
        return self._datastore_service.delete_value(name, local)
75
76
77
class PythonActionWrapper(object):
78
    def __init__(self, pack, file_path, parameters=None, user=None, parent_args=None):
0 ignored issues
show
Comprehensibility Bug introduced by
user is re-defining a name which is already available in the outer-scope (previously defined on line 187).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
Comprehensibility Bug introduced by
parent_args is re-defining a name which is already available in the outer-scope (previously defined on line 188).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
Comprehensibility Bug introduced by
parameters is re-defining a name which is already available in the outer-scope (previously defined on line 185).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
79
        """
80
        :param pack: Name of the pack this action belongs to.
81
        :type pack: ``str``
82
83
        :param file_path: Path to the action module.
84
        :type file_path: ``str``
85
86
        :param parameters: action parameters.
87
        :type parameters: ``dict`` or ``None``
88
89
        :param user: Name of the user who triggered this action execution.
90
        :type user: ``str``
91
92
        :param parent_args: Command line arguments passed to the parent process.
93
        :type parse_args: ``list``
94
        """
95
96
        self._pack = pack
97
        self._file_path = file_path
98
        self._parameters = parameters or {}
99
        self._user = user
100
        self._parent_args = parent_args or []
101
        self._class_name = None
102
        self._logger = logging.getLogger('PythonActionWrapper')
103
104
        try:
105
            config.parse_args(args=self._parent_args)
106
        except Exception:
107
            pass
108
109
        db_setup()
110
111
        # Note: We can only set a default user value if one is not provided after parsing the
112
        # config
113
        if not self._user:
114
            self._user = cfg.CONF.system_user.user
115
116
    def run(self):
117
        action = self._get_action_instance()
118
        output = action.run(**self._parameters)
119
        action_status = None
120
        if type(output) is tuple and len(output) == 2:
121
            action_status = output[0]
122
            action_result = output[1]
123
        else:
124
            action_result = output
125
126
        action_output = {"result": action_result}
127
128
        # Print output to stdout so the parent can capture it
129
        sys.stdout.write(ACTION_OUTPUT_RESULT_DELIMITER)
130
        print_output = None
131
        if action_status is None:
132
            print_output = json.dumps(action_output)
133
        elif type(action_status) is bool:
134
            action_output['status'] = action_status
135
            print_output = json.dumps(action_output)
136
        else:
137
            try:
138
                raise InvalidStatusException('Status returned from the action'
139
                                             ' must either be True or False.')
140
            except:
141
                traceback.print_exc()
142
                sys.exit(PYTHON_ACTION_INVALID_STATUS_EXIT_CODE)
143
144
        sys.stdout.write(print_output + '\n')
145
        sys.stdout.write(ACTION_OUTPUT_RESULT_DELIMITER)
146
147
    def _get_action_instance(self):
148
        actions_cls = action_loader.register_plugin(Action, self._file_path)
149
        action_cls = actions_cls[0] if actions_cls and len(actions_cls) > 0 else None
150
151
        if not action_cls:
152
            raise Exception('File "%s" has no action or the file doesn\'t exist.' %
153
                            (self._file_path))
154
155
        config_loader = ContentPackConfigLoader(pack_name=self._pack, user=self._user)
156
        config = config_loader.get_config()
0 ignored issues
show
Comprehensibility Bug introduced by
config is re-defining a name which is already available in the outer-scope (previously defined on line 23).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
157
158
        if config:
159
            LOG.info('Found config for action "%s"' % (self._file_path))
160
        else:
161
            LOG.info('No config found for action "%s"' % (self._file_path))
162
            config = None
163
164
        action_service = ActionService(action_wrapper=self)
165
        action_instance = get_action_class_instance(action_cls=action_cls,
166
                                                    config=config,
167
                                                    action_service=action_service)
168
        return action_instance
169
170
171
if __name__ == '__main__':
172
    parser = argparse.ArgumentParser(description='Python action runner process wrapper')
173
    parser.add_argument('--pack', required=True,
174
                        help='Name of the pack this action belongs to')
175
    parser.add_argument('--file-path', required=True,
176
                        help='Path to the action module')
177
    parser.add_argument('--parameters', required=False,
178
                        help='Serialized action parameters')
179
    parser.add_argument('--user', required=False,
180
                        help='User who triggered the action execution')
181
    parser.add_argument('--parent-args', required=False,
182
                        help='Command line arguments passed to the parent process')
183
    args = parser.parse_args()
184
185
    parameters = args.parameters
186
    parameters = json.loads(parameters) if parameters else {}
187
    user = args.user
188
    parent_args = json.loads(args.parent_args) if args.parent_args else []
189
190
    assert isinstance(parent_args, list)
191
    obj = PythonActionWrapper(pack=args.pack,
192
                              file_path=args.file_path,
193
                              parameters=parameters,
194
                              user=user,
195
                              parent_args=parent_args)
196
197
    obj.run()
198