holado 0.7.1__py3-none-any.whl → 0.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of holado might be problematic. Click here for more details.
- holado/common/context/context.py +107 -4
- holado/common/context/session_context.py +15 -5
- {holado-0.7.1.dist-info → holado-0.8.1.dist-info}/METADATA +6 -10
- {holado-0.7.1.dist-info → holado-0.8.1.dist-info}/RECORD +35 -30
- holado_context/tests/behave/steps/private/common/context_steps.py +35 -3
- holado_core/common/resource/persisted_method_to_call_manager.py +134 -27
- holado_core/common/tools/path_manager.py +47 -26
- holado_docker/tests/behave/steps/tools/docker_controller/client_steps.py +12 -9
- holado_docker/tools/docker_controller/client/rest/docker_controller_client.py +53 -5
- holado_docker/tools/docker_controller/server/rest/api/config.py +57 -0
- holado_docker/tools/docker_controller/server/rest/api/container.py +5 -5
- holado_docker/tools/docker_controller/server/rest/api/os.py +41 -0
- holado_docker/tools/docker_controller/server/rest/openapi.yaml +111 -0
- holado_docker/tools/docker_controller/server/run_docker_controller_in_docker.sh +2 -1
- holado_helper/docker/run_holado_test_nonreg_in_docker.sh +1 -1
- holado_helper/docker/run_terminal_in_docker.sh +1 -1
- holado_json/tests/behave/steps/ipc/json_steps.py +11 -0
- holado_python/common/iterables.py +8 -0
- holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +16 -16
- holado_python/standard_library/ssl/resources/certificates/tcpbin.key +26 -26
- holado_rabbitmq/tools/rabbitmq/rabbitmq_client.py +0 -2
- holado_system/tests/behave/steps/system/file_steps.py +26 -8
- holado_test/common/context/scenario_context.py +3 -64
- holado_test/scenario/step_tools.py +22 -0
- holado_yaml/tests/behave/steps/yaml_steps.py +106 -9
- holado_yaml/yaml/enums.py +28 -0
- holado_yaml/yaml/pyyaml/pyyaml_client.py +72 -0
- holado_yaml/yaml/ruamel/ruamel_yaml_client.py +80 -0
- holado_yaml/yaml/yaml_client.py +195 -0
- holado_yaml/yaml/yaml_manager.py +57 -49
- test_holado/features/NonReg/holado_yaml/yaml.feature +304 -8
- test_holado/features/NonReg/scenario/scenario.feature +48 -4
- test_holado/logging.conf +1 -0
- holado_helper/docker/run_terminal_in_docker-with_docker_control.sh +0 -103
- {holado-0.7.1.dist-info → holado-0.8.1.dist-info}/WHEEL +0 -0
- {holado-0.7.1.dist-info → holado-0.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -17,16 +17,31 @@ from holado_core.common.resource.persisted_data_manager import PersistedDataMana
|
|
|
17
17
|
from holado_python.standard_library.typing import Typing
|
|
18
18
|
from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
19
19
|
from holado_core.common.tools.tools import Tools
|
|
20
|
+
from holado.common.handlers.undefined import undefined_value, undefined_argument
|
|
21
|
+
from holado_python.common.tools.datetime import DateTime
|
|
22
|
+
from holado_db.tools.db.query.base.query_builder import DBCompareOperator
|
|
23
|
+
from holado.common.handlers.enums import AutoNumber
|
|
20
24
|
|
|
21
25
|
logger = logging.getLogger(__name__)
|
|
22
26
|
|
|
23
27
|
|
|
28
|
+
class MethodCallStatus(AutoNumber):
|
|
29
|
+
Ready = ()
|
|
30
|
+
Completed = ()
|
|
31
|
+
Failed = ()
|
|
32
|
+
Expired = ()
|
|
33
|
+
|
|
24
34
|
|
|
25
35
|
class PersistedMethodToCallManager(PersistedDataManager):
|
|
26
|
-
def __init__(self, data_name="method", table_name="method_to_call", db_name="default"):
|
|
36
|
+
def __init__(self, scope_name, delete_on_success=True, delete_on_fail=False, delete_on_success_after_fail=False, expiration_try_counter=10, data_name="method", table_name="method_to_call", db_name="default"):
|
|
27
37
|
super().__init__(data_name=data_name, table_name=table_name,
|
|
28
38
|
table_sql_create=self._get_default_table_sql_create(table_name),
|
|
29
39
|
db_name=db_name)
|
|
40
|
+
self.__scope_name = scope_name
|
|
41
|
+
self.__delete_on_success = delete_on_success
|
|
42
|
+
self.__delete_on_fail = delete_on_fail
|
|
43
|
+
self.__delete_on_success_after_fail = delete_on_fail
|
|
44
|
+
self.__expiration_try_counter = expiration_try_counter
|
|
30
45
|
|
|
31
46
|
def initialize(self, resource_manager, expression_evaluator):
|
|
32
47
|
super().initialize(resource_manager)
|
|
@@ -35,91 +50,183 @@ class PersistedMethodToCallManager(PersistedDataManager):
|
|
|
35
50
|
def _get_default_table_sql_create(self, table_name):
|
|
36
51
|
return f"""CREATE TABLE {table_name} (
|
|
37
52
|
id INTEGER PRIMARY KEY,
|
|
53
|
+
scope_name text NOT NULL,
|
|
38
54
|
function_qualname text NOT NULL,
|
|
39
|
-
self_getter
|
|
40
|
-
args
|
|
41
|
-
kwargs
|
|
55
|
+
self_getter TEXT,
|
|
56
|
+
args TEXT,
|
|
57
|
+
kwargs TEXT,
|
|
42
58
|
use text NOT NULL,
|
|
43
|
-
use_index integer NOT NULL
|
|
59
|
+
use_index integer NOT NULL,
|
|
60
|
+
created_at TEXT,
|
|
61
|
+
changed_at TEXT,
|
|
62
|
+
status TEXT,
|
|
63
|
+
try_counter INTEGER,
|
|
64
|
+
error TEXT,
|
|
65
|
+
do_delete_on_success INTEGER,
|
|
66
|
+
do_delete_on_fail INTEGER,
|
|
67
|
+
do_delete_on_success_after_fail INTEGER
|
|
44
68
|
)"""
|
|
45
69
|
|
|
46
|
-
def add_function_to_call(self, function_qualname, args_list=None, kwargs_dict=None, use="default", use_index=0):
|
|
70
|
+
def add_function_to_call(self, function_qualname, args_list=None, kwargs_dict=None, use="default", use_index=0, add_if_exists=False, delete_on_success=undefined_argument, delete_on_fail=undefined_argument, delete_on_success_after_fail=undefined_argument):
|
|
47
71
|
"""Add a function to call.
|
|
48
72
|
@param function_qualname: Qualified name of function
|
|
49
73
|
@param args_list: List of function args (default: None)
|
|
50
74
|
@param kwargs_dict: Dict of function kwargs (default: None)
|
|
51
75
|
@param use: Define persistent usage. It usually corresponds to a specific scope.
|
|
52
76
|
@param use_index: use index, useable to order the functions to call. By default all are 0. If set to None, it is automatically set to max(use_index)+1.
|
|
77
|
+
@param delete_on_success: If True, delete function after its success
|
|
78
|
+
@param delete_on_fail: If True, delete function after its fail
|
|
79
|
+
@param delete_on_success_after_fail: If True, delete function after its success even if it has previously failed
|
|
53
80
|
"""
|
|
54
|
-
self.add_method_to_call(function_qualname, None, args_list, kwargs_dict, use, use_index)
|
|
81
|
+
self.add_method_to_call(function_qualname, None, args_list, kwargs_dict, use, use_index, add_if_exists, delete_on_success, delete_on_fail, delete_on_success_after_fail)
|
|
55
82
|
|
|
56
|
-
def add_method_to_call(self, function_qualname, self_getter_eval_str, args_list=None, kwargs_dict=None, use="default", use_index=0):
|
|
83
|
+
def add_method_to_call(self, function_qualname, self_getter_eval_str, args_list=None, kwargs_dict=None, use="default", use_index=0, add_if_exists=False, delete_on_success=undefined_argument, delete_on_fail=undefined_argument, delete_on_success_after_fail=undefined_argument):
|
|
57
84
|
"""Add a method to call.
|
|
58
85
|
@param function_qualname: Qualified name of function
|
|
59
86
|
@param self_getter_eval_str: String to eval in order to get the self instance to use when calling method
|
|
60
87
|
@param args_list: List of function args (default: None)
|
|
61
88
|
@param kwargs_dict: Dict of function kwargs (default: None)
|
|
62
89
|
@param use: Define persistent usage. It usually corresponds to a specific scope.
|
|
63
|
-
@param use_index:
|
|
90
|
+
@param use_index: Use index, useable to order the functions to call. By default all are 0. If set to None, it is automatically set to max(use_index)+1.
|
|
91
|
+
@param add_if_exists: Add if method already exists (default: False)
|
|
92
|
+
@param delete_on_success: If True, delete method after its success
|
|
93
|
+
@param delete_on_fail: If True, delete method after its fail
|
|
94
|
+
@param delete_on_success_after_fail: If True, delete method after its success even if it has previously failed
|
|
64
95
|
"""
|
|
65
|
-
if
|
|
66
|
-
use_index = self.__get_use_next_index(use)
|
|
67
|
-
|
|
96
|
+
# Define persisted data that can define if method is already persisted
|
|
68
97
|
data = {
|
|
98
|
+
'scope_name': self.__scope_name,
|
|
69
99
|
'function_qualname': function_qualname,
|
|
70
100
|
'self_getter': self_getter_eval_str,
|
|
71
101
|
'use': use,
|
|
72
|
-
'use_index': use_index
|
|
73
102
|
}
|
|
74
103
|
if args_list is not None:
|
|
75
104
|
data['args'] = json.dumps(args_list)
|
|
76
105
|
if kwargs_dict is not None:
|
|
77
106
|
data['kwargs'] = json.dumps(kwargs_dict)
|
|
78
|
-
|
|
107
|
+
|
|
108
|
+
# Return if data is already persisted and add_if_exists==True
|
|
109
|
+
filter_compare_data = [('status', DBCompareOperator.In, [MethodCallStatus.Ready.name, MethodCallStatus.Failed.name])]
|
|
110
|
+
if not add_if_exists and self.has_persisted_data(filter_data=data, filter_compare_data=filter_compare_data):
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
# Persist data
|
|
114
|
+
if use_index is None:
|
|
115
|
+
use_index = self.__get_use_next_index(use)
|
|
116
|
+
now_str = DateTime.datetime_2_str(DateTime.utcnow())
|
|
117
|
+
data.update({
|
|
118
|
+
'use_index': use_index,
|
|
119
|
+
'created_at': now_str,
|
|
120
|
+
'changed_at': now_str,
|
|
121
|
+
'status': MethodCallStatus.Ready.name,
|
|
122
|
+
'try_counter': 0,
|
|
123
|
+
'do_delete_on_success': self.__get_db_on_delete_after_when_add_method(delete_on_success, self.__delete_on_success),
|
|
124
|
+
'do_delete_on_fail': self.__get_db_on_delete_after_when_add_method(delete_on_fail, self.__delete_on_fail),
|
|
125
|
+
'do_delete_on_success_after_fail': self.__get_db_on_delete_after_when_add_method(delete_on_success_after_fail, self.__delete_on_success_after_fail)
|
|
126
|
+
})
|
|
127
|
+
|
|
79
128
|
self.add_persisted_data(data)
|
|
80
129
|
|
|
130
|
+
def get_number_of_functions_and_method_to_call(self, use=undefined_argument):
|
|
131
|
+
methods_data = self.__get_functions_and_methods_to_call(use=use)
|
|
132
|
+
return len(methods_data)
|
|
133
|
+
|
|
134
|
+
def __get_db_on_delete_after_when_add_method(self, method_delete, default_delete):
|
|
135
|
+
if method_delete is not undefined_argument:
|
|
136
|
+
if method_delete is undefined_value:
|
|
137
|
+
return -1
|
|
138
|
+
else:
|
|
139
|
+
return method_delete
|
|
140
|
+
else:
|
|
141
|
+
if default_delete is undefined_value:
|
|
142
|
+
return -1
|
|
143
|
+
else:
|
|
144
|
+
return default_delete
|
|
145
|
+
|
|
146
|
+
def __get_do_delete_after_call(self, db_delete, default_delete):
|
|
147
|
+
if db_delete != -1:
|
|
148
|
+
return db_delete
|
|
149
|
+
else:
|
|
150
|
+
if default_delete is undefined_value:
|
|
151
|
+
return False
|
|
152
|
+
else:
|
|
153
|
+
return default_delete
|
|
154
|
+
|
|
81
155
|
def __get_use_next_index(self, use):
|
|
82
|
-
datas = self.get_persisted_datas({'use':use})
|
|
156
|
+
datas = self.get_persisted_datas({'scope_name':self.__scope_name, 'use':use})
|
|
83
157
|
if datas:
|
|
84
|
-
return max(map(lambda x:x['
|
|
158
|
+
return max(map(lambda x:x['use_index'], datas)) + 1
|
|
85
159
|
else:
|
|
86
160
|
return 0
|
|
87
161
|
|
|
88
|
-
def
|
|
162
|
+
def __get_functions_and_methods_to_call(self, use=undefined_argument, use_index=None):
|
|
163
|
+
filter_data = {'scope_name':self.__scope_name}
|
|
164
|
+
if use is not undefined_argument:
|
|
165
|
+
filter_data['use'] = use
|
|
166
|
+
if use_index is not None:
|
|
167
|
+
filter_data['use_index'] = use_index
|
|
168
|
+
filter_compare_data = [('status', DBCompareOperator.In, [MethodCallStatus.Ready.name, MethodCallStatus.Failed.name])]
|
|
169
|
+
return self.get_persisted_datas(filter_data, filter_compare_data=filter_compare_data)
|
|
170
|
+
|
|
171
|
+
def call_functions_and_methods(self, use="default", use_index=None):
|
|
89
172
|
"""Call methods of given use
|
|
90
173
|
@param use: Define persistent usage. It usually corresponds to a specific scope.
|
|
91
174
|
@param use_index: If defined, call only functions and methods of given index.
|
|
92
175
|
@param delete_after_call: Define if function or method is deleted after call from persisted data.
|
|
93
176
|
"""
|
|
94
177
|
# Get functions and methods to call
|
|
95
|
-
|
|
96
|
-
if use_index is not None:
|
|
97
|
-
filter_data['use_index'] = use_index
|
|
98
|
-
methods_data = self.get_persisted_datas(filter_data)
|
|
178
|
+
methods_data = self.__get_functions_and_methods_to_call(use=use)
|
|
99
179
|
|
|
100
180
|
# Call methods
|
|
101
181
|
if methods_data:
|
|
102
182
|
for meth_index, meth_data in enumerate(methods_data):
|
|
103
|
-
do_delete =
|
|
183
|
+
status, do_delete, error = None, None, ''
|
|
104
184
|
try:
|
|
105
185
|
self._call_function_or_method(meth_data)
|
|
106
186
|
except Exception as exc:
|
|
107
|
-
|
|
187
|
+
error = Tools.represent_exception(exc, indent=8)
|
|
188
|
+
msg_list = [f"Error while calling following method (use: '{use}' ; use index: {use_index} ; method index: {meth_index}):"]
|
|
108
189
|
msg_list.append(Tools.represent_object(meth_data, 8))
|
|
109
190
|
msg_list.append(" Error:")
|
|
110
|
-
msg_list.append(
|
|
191
|
+
msg_list.append(error)
|
|
111
192
|
msg_list.append(" => Continue to process persisted methods")
|
|
112
193
|
msg_list.append(" WARNING: this method is removed from persisted methods to avoid recursive and blocking errors")
|
|
113
194
|
logger.error("\n".join(msg_list))
|
|
114
|
-
|
|
195
|
+
status = MethodCallStatus.Failed
|
|
196
|
+
do_delete = self.__get_do_delete_after_call(meth_data['do_delete_on_fail'], self.__delete_on_fail)
|
|
197
|
+
else:
|
|
198
|
+
status = MethodCallStatus.Completed
|
|
199
|
+
if meth_data['try_counter'] == 0:
|
|
200
|
+
do_delete = self.__get_do_delete_after_call(meth_data['do_delete_on_success'], self.__delete_on_success)
|
|
201
|
+
else:
|
|
202
|
+
do_delete = self.__get_do_delete_after_call(meth_data['do_delete_on_success_after_fail'], self.__delete_on_success_after_fail)
|
|
203
|
+
|
|
204
|
+
self.__update_persisted_data_after_call(do_delete, meth_data, status, error)
|
|
115
205
|
|
|
116
|
-
|
|
117
|
-
|
|
206
|
+
def __update_persisted_data_after_call(self, do_delete, meth_data, status, error):
|
|
207
|
+
if do_delete:
|
|
208
|
+
self.__delete_function_or_method(meth_data)
|
|
209
|
+
else:
|
|
210
|
+
self.__update_function_or_method_status(meth_data, status, error)
|
|
118
211
|
|
|
119
212
|
def __delete_function_or_method(self, function_or_method_data):
|
|
120
213
|
filter_data = {'id':function_or_method_data['id']}
|
|
121
214
|
self.delete_persisted_data(filter_data)
|
|
122
215
|
|
|
216
|
+
def __update_function_or_method_status(self, function_or_method_data, status, error):
|
|
217
|
+
filter_data = {'id':function_or_method_data['id']}
|
|
218
|
+
try_counter = function_or_method_data['try_counter'] + 1
|
|
219
|
+
if try_counter >= self.__expiration_try_counter and status == MethodCallStatus.Failed:
|
|
220
|
+
status = MethodCallStatus.Expired
|
|
221
|
+
|
|
222
|
+
data = {
|
|
223
|
+
'status': status.name,
|
|
224
|
+
'try_counter': try_counter,
|
|
225
|
+
'error': error,
|
|
226
|
+
'changed_at': DateTime.datetime_2_str(DateTime.utcnow()),
|
|
227
|
+
}
|
|
228
|
+
self.update_persisted_data(data, filter_data=filter_data)
|
|
229
|
+
|
|
123
230
|
def _call_function_or_method(self, function_or_method_data):
|
|
124
231
|
_, func = self.__expression_evaluator.evaluate_python_expression(function_or_method_data['function_qualname'])
|
|
125
232
|
if not Typing.is_function(func):
|
|
@@ -20,6 +20,7 @@ from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
|
20
20
|
from holado_core.common.exceptions.functional_exception import FunctionalException
|
|
21
21
|
from datetime import datetime
|
|
22
22
|
from holado_core.common.tools.tools import Tools
|
|
23
|
+
from pathlib import Path
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
|
|
@@ -28,8 +29,15 @@ class PathManager(object):
|
|
|
28
29
|
|
|
29
30
|
def __init__(self):
|
|
30
31
|
pass
|
|
31
|
-
|
|
32
|
-
def
|
|
32
|
+
|
|
33
|
+
def get_user_group(self, path):
|
|
34
|
+
p = Path(path)
|
|
35
|
+
return p.owner(), p.group()
|
|
36
|
+
|
|
37
|
+
def get_mode(self, path):
|
|
38
|
+
return os.stat(path).st_mode & 0o777
|
|
39
|
+
|
|
40
|
+
def makedirs(self, path, mode = None, is_directory = False, user = None, group = None):
|
|
33
41
|
if not is_directory:
|
|
34
42
|
path = os.path.dirname(path)
|
|
35
43
|
if not os.path.exists(path):
|
|
@@ -37,36 +45,48 @@ class PathManager(object):
|
|
|
37
45
|
os.makedirs(path, mode)
|
|
38
46
|
else:
|
|
39
47
|
os.makedirs(path)
|
|
40
|
-
self.chown(path,
|
|
48
|
+
self.chown(path, user, group)
|
|
49
|
+
|
|
50
|
+
def makefile(self, path, mode = None, user = None, group = None):
|
|
51
|
+
if not os.path.exists(path):
|
|
52
|
+
self.makedirs(path, mode, False, user, group)
|
|
53
|
+
open(path, 'a').close()
|
|
54
|
+
|
|
55
|
+
if mode:
|
|
56
|
+
os.chmod(path, mode)
|
|
57
|
+
self.chown(path, user, group)
|
|
58
|
+
|
|
59
|
+
def remove_path(self, path, ignore_errors=False):
|
|
60
|
+
if os.path.isfile(path):
|
|
61
|
+
try:
|
|
62
|
+
os.remove(path)
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
if not ignore_errors:
|
|
65
|
+
raise exc
|
|
66
|
+
elif os.path.isdir(path):
|
|
67
|
+
shutil.rmtree(path, ignore_errors=ignore_errors)
|
|
41
68
|
|
|
42
69
|
def remove_paths(self, glob_pattern, ignore_errors=False):
|
|
43
70
|
paths = glob(glob_pattern)
|
|
44
71
|
for path in paths:
|
|
45
|
-
|
|
46
|
-
try:
|
|
47
|
-
os.remove(path)
|
|
48
|
-
except Exception as exc:
|
|
49
|
-
if not ignore_errors:
|
|
50
|
-
raise exc
|
|
51
|
-
elif os.path.isdir(path):
|
|
52
|
-
shutil.rmtree(path, ignore_errors=ignore_errors)
|
|
72
|
+
self.remove_path(path, ignore_errors)
|
|
53
73
|
|
|
54
|
-
def copy_path_recursively(self, src_path, dst_path, filter_patterns = None, ignore_patterns = None, do_log = False, log_prefix = "",
|
|
74
|
+
def copy_path_recursively(self, src_path, dst_path, filter_patterns = None, ignore_patterns = None, do_log = False, log_prefix = "", user = None, group = None):
|
|
55
75
|
# logging.debug("Copying path '{}' -> '{}' (ignoring {})".format(src_path, dst_path, ignore_patterns))
|
|
56
76
|
# Copy path
|
|
57
77
|
if os.path.isfile(src_path):
|
|
58
78
|
if self.__filter_path(src_path, filter_patterns=filter_patterns, ignore_patterns=ignore_patterns, do_log=do_log, log_prefix=log_prefix):
|
|
59
79
|
if do_log:
|
|
60
80
|
logger.debug("{}Copy file '{}' -> '{}".format(log_prefix, src_path, dst_path))
|
|
61
|
-
self.makedirs(dst_path,
|
|
81
|
+
self.makedirs(dst_path, user=user, group=group)
|
|
62
82
|
shutil.copy2(src_path, dst_path)
|
|
63
|
-
self.chown(dst_path,
|
|
83
|
+
self.chown(dst_path, user, group)
|
|
64
84
|
elif os.path.isdir(src_path):
|
|
65
85
|
lp = os.listdir(src_path)
|
|
66
86
|
for cp in lp:
|
|
67
87
|
cur_src_path = os.path.join(src_path, cp)
|
|
68
88
|
cur_dst_path = os.path.join(dst_path, cp)
|
|
69
|
-
self.copy_path_recursively(cur_src_path, cur_dst_path, filter_patterns=filter_patterns, ignore_patterns=ignore_patterns, do_log=do_log, log_prefix=log_prefix,
|
|
89
|
+
self.copy_path_recursively(cur_src_path, cur_dst_path, filter_patterns=filter_patterns, ignore_patterns=ignore_patterns, do_log=do_log, log_prefix=log_prefix, user=user, group=group)
|
|
70
90
|
|
|
71
91
|
def __filter_paths(self, paths, filter_patterns = None, ignore_patterns = None, do_log = False, log_prefix = ""):
|
|
72
92
|
if filter_patterns is not None and not isinstance(filter_patterns, list):
|
|
@@ -146,22 +166,23 @@ class PathManager(object):
|
|
|
146
166
|
else:
|
|
147
167
|
return os.path.join(base_path, "reports")
|
|
148
168
|
|
|
149
|
-
def chown(self, path,
|
|
150
|
-
if
|
|
151
|
-
if
|
|
152
|
-
raise TechnicalException(f"User and group ID cannot be None (
|
|
153
|
-
|
|
169
|
+
def chown(self, path, user = None, group = None):
|
|
170
|
+
if user is not None or group is not None:
|
|
171
|
+
if user is None or group is None:
|
|
172
|
+
raise TechnicalException(f"User and group (name or ID) cannot be None (user={user} ; group={group})")
|
|
173
|
+
shutil.chown(path, user, group)
|
|
154
174
|
|
|
155
|
-
def check_file_exists(self, path, raise_exception=True):
|
|
175
|
+
def check_file_exists(self, path, do_exist=True, raise_exception=True):
|
|
156
176
|
"""
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
177
|
+
@param path Path
|
|
178
|
+
@param do_exist Define to check if file exists or not
|
|
179
|
+
@param raise_exception If True, raises an exception rather than returning False
|
|
180
|
+
@return True if given path is an existing file
|
|
160
181
|
"""
|
|
161
|
-
res = os.path.exists(path)
|
|
182
|
+
res = (do_exist == os.path.exists(path))
|
|
162
183
|
|
|
163
184
|
if not res and raise_exception:
|
|
164
|
-
raise FunctionalException(f"File '{path}' doesn't exist")
|
|
185
|
+
raise FunctionalException(f"File '{path}' " + "doesn't exist" if do_exist else "exists")
|
|
165
186
|
return res
|
|
166
187
|
|
|
167
188
|
def get_timestamped_path(self, prefix, ext, dt=None, dt_format="%Y%m%d-%H%M%S"):
|
|
@@ -63,12 +63,13 @@ def step_impl(context, var_name, name, var_client):
|
|
|
63
63
|
|
|
64
64
|
__get_variable_manager().register_variable(var_name, res)
|
|
65
65
|
|
|
66
|
-
@Step(r"restart container (?P<name>{Str}) \(Docker Controller client: (?P<var_client>{Variable})\)")
|
|
67
|
-
def step_impl(context, name, var_client):
|
|
66
|
+
@Step(r"(?:(?P<or_start_str>start or ))?restart container (?P<name>{Str}) \(Docker Controller client: (?P<var_client>{Variable})\)")
|
|
67
|
+
def step_impl(context, or_start_str, name, var_client):
|
|
68
|
+
or_start = or_start_str is not None
|
|
68
69
|
name = StepTools.evaluate_scenario_parameter(name)
|
|
69
70
|
client = StepTools.evaluate_variable_value(var_client)
|
|
70
71
|
|
|
71
|
-
client.restart_container(name)
|
|
72
|
+
client.restart_container(name, start_if_gone=or_start)
|
|
72
73
|
|
|
73
74
|
@Step(r"start container (?P<name>{Str}) \(Docker Controller client: (?P<var_client>{Variable})\)")
|
|
74
75
|
def step_impl(context, name, var_client):
|
|
@@ -77,18 +78,20 @@ def step_impl(context, name, var_client):
|
|
|
77
78
|
|
|
78
79
|
client.start_container(name)
|
|
79
80
|
|
|
80
|
-
@Step(r"stop container (?P<name>{Str}) \(Docker Controller client: (?P<var_client>{Variable})\)")
|
|
81
|
-
def step_impl(context, name, var_client):
|
|
81
|
+
@Step(r"stop container (?P<name>{Str})(?:(?P<if_started_str> if started))? \(Docker Controller client: (?P<var_client>{Variable})\)")
|
|
82
|
+
def step_impl(context, name, if_started_str, var_client):
|
|
82
83
|
name = StepTools.evaluate_scenario_parameter(name)
|
|
84
|
+
if_started = if_started_str is not None
|
|
83
85
|
client = StepTools.evaluate_variable_value(var_client)
|
|
84
86
|
|
|
85
|
-
client.stop_container(name)
|
|
87
|
+
client.stop_container(name, raise_if_gone=not if_started)
|
|
86
88
|
|
|
87
|
-
@Step(r"wait container (?P<name>{Str}) \(Docker Controller client: (?P<var_client>{Variable})\)")
|
|
88
|
-
def step_impl(context, name, var_client):
|
|
89
|
+
@Step(r"wait container (?P<name>{Str})(?:(?P<if_started_str> if started))? \(Docker Controller client: (?P<var_client>{Variable})\)")
|
|
90
|
+
def step_impl(context, name, if_started_str, var_client):
|
|
89
91
|
name = StepTools.evaluate_scenario_parameter(name)
|
|
92
|
+
if_started = if_started_str is not None
|
|
90
93
|
client = StepTools.evaluate_variable_value(var_client)
|
|
91
94
|
|
|
92
|
-
client.wait_container(name)
|
|
95
|
+
client.wait_container(name, raise_if_gone=not if_started)
|
|
93
96
|
|
|
94
97
|
|
|
@@ -22,6 +22,21 @@ class DockerControllerClient(RestClient):
|
|
|
22
22
|
def __init__(self, name, url, headers=None):
|
|
23
23
|
super().__init__(name, url, headers)
|
|
24
24
|
|
|
25
|
+
# Common features
|
|
26
|
+
|
|
27
|
+
def get_environment_variable_value(self, var_name):
|
|
28
|
+
data = [var_name]
|
|
29
|
+
response = self.get(f"os/env", data=data)
|
|
30
|
+
return self.response_result(response, status_ok=[200])
|
|
31
|
+
|
|
32
|
+
def get_directory_filenames(self, path):
|
|
33
|
+
data = {'path':path}
|
|
34
|
+
response = self.get(f"os/ls", data=data)
|
|
35
|
+
return self.response_result(response, status_ok=[200])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Manage containers
|
|
39
|
+
|
|
25
40
|
def get_containers_status(self, all_=False):
|
|
26
41
|
if all_:
|
|
27
42
|
response = self.get("container?all=true")
|
|
@@ -36,19 +51,52 @@ class DockerControllerClient(RestClient):
|
|
|
36
51
|
response = self.get(f"container/{name}")
|
|
37
52
|
return self.response_result(response, status_ok=[200,204])
|
|
38
53
|
|
|
39
|
-
def restart_container(self, name):
|
|
54
|
+
def restart_container(self, name, start_if_gone=False):
|
|
40
55
|
response = self.put(f"container/{name}/restart")
|
|
41
|
-
|
|
56
|
+
if start_if_gone and response.status_code == 410:
|
|
57
|
+
return self.start_container(name)
|
|
58
|
+
else:
|
|
59
|
+
return self.response_result(response, status_ok=[200,204])
|
|
42
60
|
|
|
43
61
|
def start_container(self, name):
|
|
44
62
|
response = self.put(f"container/{name}/start")
|
|
45
63
|
return self.response_result(response, status_ok=[200,204])
|
|
46
64
|
|
|
47
|
-
def stop_container(self, name):
|
|
65
|
+
def stop_container(self, name, raise_if_gone=True):
|
|
48
66
|
response = self.put(f"container/{name}/stop")
|
|
49
|
-
|
|
67
|
+
if not raise_if_gone and response.status_code == 410:
|
|
68
|
+
return None
|
|
69
|
+
else:
|
|
70
|
+
return self.response_result(response, status_ok=[200,204])
|
|
50
71
|
|
|
51
|
-
def wait_container(self, name):
|
|
72
|
+
def wait_container(self, name, raise_if_gone=True):
|
|
52
73
|
response = self.put(f"container/{name}/wait")
|
|
74
|
+
if not raise_if_gone and response.status_code == 410:
|
|
75
|
+
return None
|
|
76
|
+
else:
|
|
77
|
+
return self.response_result(response, status_ok=[200,204])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Manage configuration
|
|
81
|
+
|
|
82
|
+
def update_yaml_file(self, file_path, text, with_backup=True, backup_extension='.ha_bak'):
|
|
83
|
+
data = {
|
|
84
|
+
'file_path': file_path,
|
|
85
|
+
'yaml_string': text,
|
|
86
|
+
'with_backup': with_backup,
|
|
87
|
+
'backup_extension': backup_extension
|
|
88
|
+
}
|
|
89
|
+
response = self.patch(f"config/yaml_file", data=data)
|
|
53
90
|
return self.response_result(response, status_ok=[200,204])
|
|
54
91
|
|
|
92
|
+
def restore_yaml_file(self, file_path, backup_extension='.ha_bak'):
|
|
93
|
+
data = {
|
|
94
|
+
'action': 'restore',
|
|
95
|
+
'file_path': file_path,
|
|
96
|
+
'backup_extension': backup_extension
|
|
97
|
+
}
|
|
98
|
+
response = self.put(f"config/yaml_file", data=data)
|
|
99
|
+
return self.response_result(response, status_ok=[200,204])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#################################################
|
|
2
|
+
# HolAdo (Holistic Automation do)
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright 2021-2025 by Eric Klumpp
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
#
|
|
8
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
|
|
10
|
+
# The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
|
|
11
|
+
#################################################
|
|
12
|
+
|
|
13
|
+
from flask.views import MethodView
|
|
14
|
+
from holado.common.context.session_context import SessionContext
|
|
15
|
+
from holado_yaml.yaml.yaml_manager import YAMLManager
|
|
16
|
+
|
|
17
|
+
def _get_session_context():
|
|
18
|
+
return SessionContext.instance()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class YamlfileView(MethodView):
|
|
22
|
+
|
|
23
|
+
def get(self, body: dict):
|
|
24
|
+
file_path = body['file_path']
|
|
25
|
+
|
|
26
|
+
with open(file_path, 'rt') as fin:
|
|
27
|
+
res = fin.read()
|
|
28
|
+
|
|
29
|
+
return res
|
|
30
|
+
|
|
31
|
+
def patch(self, body: dict):
|
|
32
|
+
file_path = body['file_path']
|
|
33
|
+
yaml_string = body['yaml_string']
|
|
34
|
+
with_backup = body['with_backup']
|
|
35
|
+
backup_extension = body['backup_extension']
|
|
36
|
+
|
|
37
|
+
data = YAMLManager.load_string(yaml_string)
|
|
38
|
+
res = YAMLManager.update_file(file_path, data, with_backup=with_backup, backup_extension=backup_extension)
|
|
39
|
+
|
|
40
|
+
return res
|
|
41
|
+
|
|
42
|
+
def put(self, body: list):
|
|
43
|
+
action = body['action']
|
|
44
|
+
file_path = body['file_path']
|
|
45
|
+
backup_extension = body['backup_extension']
|
|
46
|
+
|
|
47
|
+
if action == 'restore':
|
|
48
|
+
res = YAMLManager.restore_file(file_path, backup_extension=backup_extension)
|
|
49
|
+
else:
|
|
50
|
+
return f"Unmanaged action '{action}'", 400
|
|
51
|
+
|
|
52
|
+
return res
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
@@ -21,7 +21,7 @@ class RestartView(MethodView):
|
|
|
21
21
|
|
|
22
22
|
def put(self, name, body: dict):
|
|
23
23
|
if not _get_session_context().docker_client.has_container(name):
|
|
24
|
-
return f"Container '{name}' doesn't exist",
|
|
24
|
+
return f"Container '{name}' doesn't exist", 410
|
|
25
25
|
|
|
26
26
|
res = _get_session_context().docker_client.restart_container(name, wait_running=False)
|
|
27
27
|
return res
|
|
@@ -29,8 +29,8 @@ class RestartView(MethodView):
|
|
|
29
29
|
class StartView(MethodView):
|
|
30
30
|
|
|
31
31
|
def put(self, name, body: dict):
|
|
32
|
-
if not _get_session_context().docker_client.has_container(name):
|
|
33
|
-
return f"Container '{name}' doesn't exist",
|
|
32
|
+
if not _get_session_context().docker_client.has_container(name, all_=True):
|
|
33
|
+
return f"Container '{name}' doesn't exist", 410
|
|
34
34
|
|
|
35
35
|
res = _get_session_context().docker_client.start_container(name, wait_running=False)
|
|
36
36
|
return res
|
|
@@ -39,7 +39,7 @@ class StopView(MethodView):
|
|
|
39
39
|
|
|
40
40
|
def put(self, name, body: dict):
|
|
41
41
|
if not _get_session_context().docker_client.has_container(name):
|
|
42
|
-
return f"Container '{name}' doesn't exist",
|
|
42
|
+
return f"Container '{name}' doesn't exist", 410
|
|
43
43
|
|
|
44
44
|
res = _get_session_context().docker_client.stop_container(name)
|
|
45
45
|
return res
|
|
@@ -48,7 +48,7 @@ class WaitView(MethodView):
|
|
|
48
48
|
|
|
49
49
|
def put(self, name, body: dict):
|
|
50
50
|
if not _get_session_context().docker_client.has_container(name):
|
|
51
|
-
return f"Container '{name}' doesn't exist",
|
|
51
|
+
return f"Container '{name}' doesn't exist", 410
|
|
52
52
|
|
|
53
53
|
res = _get_session_context().docker_client.get_container(name).wait()
|
|
54
54
|
return res
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#################################################
|
|
2
|
+
# HolAdo (Holistic Automation do)
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright 2021-2025 by Eric Klumpp
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
#
|
|
8
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
|
|
10
|
+
# The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
|
|
11
|
+
#################################################
|
|
12
|
+
|
|
13
|
+
from flask.views import MethodView
|
|
14
|
+
from holado.common.context.session_context import SessionContext
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
def _get_session_context():
|
|
18
|
+
return SessionContext.instance()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EnvView(MethodView):
|
|
22
|
+
|
|
23
|
+
def get(self, body: list):
|
|
24
|
+
res = [os.getenv(name) for name in body]
|
|
25
|
+
return res
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LsView(MethodView):
|
|
29
|
+
|
|
30
|
+
def get(self, body: dict):
|
|
31
|
+
dir_path = body['path']
|
|
32
|
+
|
|
33
|
+
res = []
|
|
34
|
+
for filename in os.listdir(dir_path):
|
|
35
|
+
if os.path.isfile(os.path.join(dir_path, filename)):
|
|
36
|
+
res.append(filename)
|
|
37
|
+
|
|
38
|
+
return res
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|