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.

Files changed (36) hide show
  1. holado/common/context/context.py +107 -4
  2. holado/common/context/session_context.py +15 -5
  3. {holado-0.7.1.dist-info → holado-0.8.1.dist-info}/METADATA +6 -10
  4. {holado-0.7.1.dist-info → holado-0.8.1.dist-info}/RECORD +35 -30
  5. holado_context/tests/behave/steps/private/common/context_steps.py +35 -3
  6. holado_core/common/resource/persisted_method_to_call_manager.py +134 -27
  7. holado_core/common/tools/path_manager.py +47 -26
  8. holado_docker/tests/behave/steps/tools/docker_controller/client_steps.py +12 -9
  9. holado_docker/tools/docker_controller/client/rest/docker_controller_client.py +53 -5
  10. holado_docker/tools/docker_controller/server/rest/api/config.py +57 -0
  11. holado_docker/tools/docker_controller/server/rest/api/container.py +5 -5
  12. holado_docker/tools/docker_controller/server/rest/api/os.py +41 -0
  13. holado_docker/tools/docker_controller/server/rest/openapi.yaml +111 -0
  14. holado_docker/tools/docker_controller/server/run_docker_controller_in_docker.sh +2 -1
  15. holado_helper/docker/run_holado_test_nonreg_in_docker.sh +1 -1
  16. holado_helper/docker/run_terminal_in_docker.sh +1 -1
  17. holado_json/tests/behave/steps/ipc/json_steps.py +11 -0
  18. holado_python/common/iterables.py +8 -0
  19. holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +16 -16
  20. holado_python/standard_library/ssl/resources/certificates/tcpbin.key +26 -26
  21. holado_rabbitmq/tools/rabbitmq/rabbitmq_client.py +0 -2
  22. holado_system/tests/behave/steps/system/file_steps.py +26 -8
  23. holado_test/common/context/scenario_context.py +3 -64
  24. holado_test/scenario/step_tools.py +22 -0
  25. holado_yaml/tests/behave/steps/yaml_steps.py +106 -9
  26. holado_yaml/yaml/enums.py +28 -0
  27. holado_yaml/yaml/pyyaml/pyyaml_client.py +72 -0
  28. holado_yaml/yaml/ruamel/ruamel_yaml_client.py +80 -0
  29. holado_yaml/yaml/yaml_client.py +195 -0
  30. holado_yaml/yaml/yaml_manager.py +57 -49
  31. test_holado/features/NonReg/holado_yaml/yaml.feature +304 -8
  32. test_holado/features/NonReg/scenario/scenario.feature +48 -4
  33. test_holado/logging.conf +1 -0
  34. holado_helper/docker/run_terminal_in_docker-with_docker_control.sh +0 -103
  35. {holado-0.7.1.dist-info → holado-0.8.1.dist-info}/WHEEL +0 -0
  36. {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 text,
40
- args text,
41
- kwargs text,
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: 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.
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 use_index is None:
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['a'], datas)) + 1
158
+ return max(map(lambda x:x['use_index'], datas)) + 1
85
159
  else:
86
160
  return 0
87
161
 
88
- def call_functions_and_methods(self, use="default", use_index=None, delete_after_call=False):
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
- filter_data = {'use':use}
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 = delete_after_call
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
- msg_list = [f"Error while calling following method (use: '{use}' ; use index: {use_index} ; method index: {meth_index} ; delete after call: {delete_after_call}):"]
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(Tools.represent_exception(exc, indent=8))
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
- do_delete = True
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
- if do_delete:
117
- self.__delete_function_or_method(meth_data)
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 makedirs(self, path, mode = None, is_directory = False, uid = None, gid = None):
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, uid, gid)
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
- if os.path.isfile(path):
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 = "", uid = None, gid = None):
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, uid=uid, gid=gid)
81
+ self.makedirs(dst_path, user=user, group=group)
62
82
  shutil.copy2(src_path, dst_path)
63
- self.chown(dst_path, uid, gid)
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, uid=uid, gid=gid)
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, uid = None, gid = None):
150
- if uid is not None or gid is not None:
151
- if uid is None or gid is None:
152
- raise TechnicalException(f"User and group ID cannot be None (uid={uid} ; gid={gid})")
153
- os.chown(path, uid, gid)
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
- * @param path Path
158
- * @param throwException If True, raises an exception rather than returning False
159
- * @return True if given path is an existing file
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
- return self.response_result(response, status_ok=[200,204])
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
- return self.response_result(response, status_ok=[200,204])
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", 406
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", 406
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", 406
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", 406
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
+