zrb 0.26.0__py3-none-any.whl → 0.28.0__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.
Files changed (45) hide show
  1. zrb/__init__.py +0 -3
  2. zrb/action/runner.py +0 -2
  3. zrb/builtin/devtool/install/zsh/install.sh +1 -1
  4. zrb/builtin/monorepo/_common.sh +15 -0
  5. zrb/builtin/monorepo/_config.py +2 -2
  6. zrb/builtin/monorepo/_helper.py +140 -0
  7. zrb/builtin/monorepo/_task.py +98 -71
  8. zrb/builtin/monorepo/add-subrepo.sh +22 -0
  9. zrb/builtin/monorepo/add.py +9 -1
  10. zrb/builtin/monorepo/pull-monorepo.sh +18 -0
  11. zrb/builtin/monorepo/pull-subrepo.sh +18 -0
  12. zrb/builtin/monorepo/pull.py +2 -1
  13. zrb/builtin/monorepo/push-monorepo.sh +18 -0
  14. zrb/builtin/monorepo/push-subrepo.sh +18 -0
  15. zrb/builtin/monorepo/push.py +3 -14
  16. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/microservices/start.py +7 -2
  17. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/monolith/start.py +7 -2
  18. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/remove.py +8 -2
  19. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/stop.py +8 -2
  20. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/support/start.py +15 -3
  21. zrb/config/config.py +0 -9
  22. zrb/helper/asyncio_task.py +19 -6
  23. zrb/helper/cli.py +0 -1
  24. zrb/task/base_task/base_task.py +39 -9
  25. zrb/task/cmd_task.py +9 -10
  26. zrb/task/docker_compose_start_task.py +30 -27
  27. zrb/task/docker_compose_task.py +64 -33
  28. zrb/task/flow_task.py +0 -1
  29. zrb/task/looper.py +1 -1
  30. zrb/task/remote_cmd_task.py +55 -18
  31. zrb/task/rsync_task.py +57 -23
  32. {zrb-0.26.0.dist-info → zrb-0.28.0.dist-info}/METADATA +2 -2
  33. {zrb-0.26.0.dist-info → zrb-0.28.0.dist-info}/RECORD +36 -38
  34. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/_helper.py +0 -8
  35. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/microservices/_helper.py +0 -24
  36. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/monolith/_helper.py +0 -22
  37. zrb/builtin/project/add/fastapp/app/template/_automate/snake_zrb_app_name/container/support/_helper.py +0 -30
  38. zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/.generator-version +0 -1
  39. zrb/shell-scripts/ensure-podman-is-installed.sh +0 -55
  40. zrb/shell-scripts/rsync-util.sh +0 -12
  41. zrb/shell-scripts/ssh-util.sh +0 -12
  42. zrb/task/base_remote_cmd_task.py +0 -354
  43. {zrb-0.26.0.dist-info → zrb-0.28.0.dist-info}/LICENSE +0 -0
  44. {zrb-0.26.0.dist-info → zrb-0.28.0.dist-info}/WHEEL +0 -0
  45. {zrb-0.26.0.dist-info → zrb-0.28.0.dist-info}/entry_points.txt +0 -0
@@ -17,7 +17,6 @@ from .._input import enable_monitoring_input
17
17
  from .._service_config import snake_zrb_app_name_service_configs
18
18
  from ..remove import remove_snake_zrb_app_name_container
19
19
  from ._group import snake_zrb_app_name_support_container_group
20
- from ._helper import activate_support_compose_profile, should_start_support_container
21
20
 
22
21
  start_snake_zrb_app_name_support_container = DockerComposeStartTask(
23
22
  icon="🐳",
@@ -31,10 +30,23 @@ start_snake_zrb_app_name_support_container = DockerComposeStartTask(
31
30
  https_input,
32
31
  image_input,
33
32
  ],
34
- should_execute=should_start_support_container,
35
33
  upstreams=[remove_snake_zrb_app_name_container],
36
34
  cwd=RESOURCE_DIR,
37
- setup_cmd=activate_support_compose_profile,
35
+ should_execute=" ".join(
36
+ [
37
+ "{{",
38
+ 'env.APP_DB_CONNECTION.startswith("postgresql")',
39
+ 'or env.APP_BROKER_TYPE in ("kafka", "rabbitmq")',
40
+ "or input.enable_snake_zrb_app_name_monitoring",
41
+ "}}",
42
+ ]
43
+ ),
44
+ compose_profiles=[
45
+ '{{"postgres" if env.APP_DB_CONNECTION.startswith("postgresql") else ""}}',
46
+ '{{"kafka" if env.APP_BROKER_TYPE == "kafka" else ""}}',
47
+ '{{"rabbitmq" if env.APP_BROKER_TYPE == "rabbitmq" else ""}}',
48
+ '{{"monitoring" if input.enable_snake_zrb_app_name_monitoring else ""}}',
49
+ ],
38
50
  compose_env_prefix="CONTAINER_ZRB_ENV_PREFIX",
39
51
  compose_service_configs=snake_zrb_app_name_service_configs,
40
52
  env_files=[compose_env_file],
zrb/config/config.py CHANGED
@@ -18,12 +18,6 @@ def _get_current_shell() -> str:
18
18
  return "bash"
19
19
 
20
20
 
21
- def _get_valid_container_backend(container_backend: str) -> str:
22
- if container_backend.lower().strip() == "podman":
23
- return "podman"
24
- return "docker"
25
-
26
-
27
21
  def _get_default_tmp_dir() -> str:
28
22
  if os.path.isdir("/tmp"):
29
23
  return "/tmp"
@@ -52,7 +46,4 @@ SHOW_ADVERTISEMENT = untyped_to_boolean(os.getenv("ZRB_SHOW_ADVERTISEMENT", "1")
52
46
  SHOW_PROMPT = untyped_to_boolean(os.getenv("ZRB_SHOW_PROMPT", "1"))
53
47
  SHOW_TIME = untyped_to_boolean(os.getenv("ZRB_SHOW_TIME", "1"))
54
48
  VERSION = _get_version()
55
- CONTAINER_BACKEND = _get_valid_container_backend(
56
- os.getenv("ZRB_CONTAINER_BACKEND", "docker")
57
- )
58
49
  ENABLE_TYPE_CHECKING = untyped_to_boolean(os.getenv("ZRB_ENABLE_TYPE_CHECKING", "1"))
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import sys
2
3
 
3
4
  from zrb.helper.accessories.color import colored
4
5
  from zrb.helper.log import logger
@@ -6,18 +7,30 @@ from zrb.helper.log import logger
6
7
  logger.debug(colored("Loading zrb.helper.asyncio_task", attrs=["dark"]))
7
8
 
8
9
 
10
+ def _surpress_event_loop_error(unraisable):
11
+ if not (
12
+ isinstance(unraisable.exc_value, RuntimeError)
13
+ and str(unraisable.exc_value) == "Event loop is closed"
14
+ ):
15
+ # Raise exception for anything except "event loop is closed"
16
+ sys.__unraisablehook__(unraisable)
17
+
18
+
19
+ sys.unraisablehook = _surpress_event_loop_error
20
+
21
+
9
22
  async def stop_asyncio():
10
23
  tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()]
11
24
  for task in tasks:
12
25
  task.cancel()
13
-
14
26
  # Wait until all tasks are cancelled
15
27
  await asyncio.gather(*tasks, return_exceptions=True)
16
28
 
17
29
 
18
30
  def stop_asyncio_sync():
19
- loop = asyncio.get_event_loop()
20
- if loop.is_running():
21
- loop.create_task(stop_asyncio())
22
- else:
23
- loop.run_until_complete(stop_asyncio())
31
+ try:
32
+ loop = asyncio.get_event_loop()
33
+ if loop.is_running():
34
+ loop.create_task(stop_asyncio())
35
+ except asyncio.CancelledError:
36
+ logger.warning("Task is cancelled")
zrb/helper/cli.py CHANGED
@@ -58,7 +58,6 @@ def create_cli() -> click.Group:
58
58
  attrs=["bold"],
59
59
  )
60
60
  )
61
- traceback.print_exc()
62
61
  # Load zrb_init.py
63
62
  project_dir = os.getenv("ZRB_PROJECT_DIR", os.getcwd())
64
63
  project_script = os.path.join(project_dir, "zrb_init.py")
@@ -159,16 +159,15 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
159
159
  def copy(self) -> AnyTask:
160
160
  return copy.deepcopy(self)
161
161
 
162
- def to_function(
162
+ def __get_async_function(
163
163
  self,
164
164
  env_prefix: str = "",
165
165
  raise_error: bool = True,
166
- is_async: bool = False,
167
166
  show_done_info: bool = True,
168
167
  should_clear_xcom: bool = False,
169
168
  should_stop_looper: bool = False,
170
169
  ) -> Callable[..., Any]:
171
- async def function(*args: Any, **kwargs: Any) -> Any:
170
+ async def async_function(*args: Any, **kwargs: Any) -> Any:
172
171
  self.log_info("Copy task")
173
172
  self_cp: AnyTask = self.copy()
174
173
  result = await self_cp._run_and_check_all(
@@ -184,9 +183,41 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
184
183
  self_cp.clear_xcom()
185
184
  return result
186
185
 
186
+ return async_function
187
+
188
+ def __to_sync_function(
189
+ self, async_function: Callable[..., Any]
190
+ ) -> Callable[..., Any]:
191
+ def sync_function(*args: Any, **kwargs: Any) -> Any:
192
+ try:
193
+ return asyncio.run(async_function(*args, **kwargs))
194
+ except RuntimeError as e:
195
+ if "event loop is closed" not in str(e).lower():
196
+ raise e
197
+ except asyncio.CancelledError:
198
+ self.print_out_dark("Task is cancelled")
199
+
200
+ return sync_function
201
+
202
+ def to_function(
203
+ self,
204
+ env_prefix: str = "",
205
+ raise_error: bool = True,
206
+ is_async: bool = False,
207
+ show_done_info: bool = True,
208
+ should_clear_xcom: bool = False,
209
+ should_stop_looper: bool = False,
210
+ ) -> Callable[..., Any]:
211
+ async_function = self.__get_async_function(
212
+ env_prefix=env_prefix,
213
+ raise_error=raise_error,
214
+ show_done_info=show_done_info,
215
+ should_clear_xcom=should_clear_xcom,
216
+ should_stop_looper=should_stop_looper,
217
+ )
187
218
  if is_async:
188
- return function
189
- return lambda *args, **kwargs: asyncio.run(function(*args, **kwargs))
219
+ return async_function
220
+ return self.__to_sync_function(async_function)
190
221
 
191
222
  async def run(self, *args: Any, **kwargs: Any) -> Any:
192
223
  if self._run_function is not None:
@@ -287,9 +318,6 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
287
318
  result = results[-1]
288
319
  self._print_result(result)
289
320
  return result
290
- except RuntimeError as e:
291
- if "event loop is closed" not in str(e).lower():
292
- raise e
293
321
  except Exception as e:
294
322
  self.log_error(f"{e}")
295
323
  if raise_error:
@@ -384,7 +412,9 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
384
412
  is_failed: bool = False
385
413
  while self._should_attempt():
386
414
  try:
387
- self.log_debug(f"Started with args: {args} and kwargs: {local_kwargs}")
415
+ self.log_debug(
416
+ f"Started with args: {args} and kwargs: {local_kwargs}"
417
+ ) # noqa
388
418
  await self.on_started()
389
419
  self.__running_tasks.append(self)
390
420
  result = await run_async(self.run, *args, **local_kwargs)
zrb/task/cmd_task.py CHANGED
@@ -291,8 +291,6 @@ class CmdTask(BaseTask):
291
291
  for pid in self._pids:
292
292
  self.__kill_by_pid(pid)
293
293
  stop_asyncio_sync()
294
- _print_out_dark(f"Exiting with signal {signum}")
295
- sys.exit(signum)
296
294
 
297
295
  def __on_exit(self):
298
296
  self._global_state.no_more_attempt = True
@@ -351,14 +349,15 @@ class CmdTask(BaseTask):
351
349
  )
352
350
  )
353
351
  # wait process
354
- await process.wait()
355
- # wait reader and logger
356
- await stdout_process
357
- await stderr_process
358
- await stdout_queue.put(None)
359
- await stderr_queue.put(None)
360
- await stdout_log_process
361
- await stderr_log_process
352
+ await asyncio.gather(
353
+ process.wait(),
354
+ stdout_process,
355
+ stderr_process,
356
+ )
357
+ # stop messages in queue
358
+ await asyncio.gather(stdout_queue.put(None), stderr_queue.put(None))
359
+ # end logging
360
+ await asyncio.gather(stdout_log_process, stderr_log_process)
362
361
 
363
362
  def get_cmd_script(self, *args: Any, **kwargs: Any) -> str:
364
363
  return self._create_cmd_script(self._cmd_path, self._cmd, *args, **kwargs)
@@ -45,6 +45,7 @@ class DockerComposeStartTask(DockerComposeTask):
45
45
  compose_options: Mapping[JinjaTemplate, JinjaTemplate] = {},
46
46
  compose_flags: Iterable[JinjaTemplate] = [],
47
47
  compose_args: Iterable[JinjaTemplate] = [],
48
+ compose_profiles: Iterable[JinjaTemplate] = [],
48
49
  compose_env_prefix: str = "",
49
50
  setup_cmd: CmdVal = "",
50
51
  setup_cmd_path: CmdVal = "",
@@ -85,9 +86,11 @@ class DockerComposeStartTask(DockerComposeTask):
85
86
  executable=executable,
86
87
  compose_service_configs=compose_service_configs,
87
88
  compose_file=compose_file,
89
+ compose_cmd="up",
88
90
  compose_options=compose_options,
89
91
  compose_flags=compose_flags,
90
92
  compose_args=compose_args,
93
+ compose_profiles=compose_profiles,
91
94
  compose_env_prefix=compose_env_prefix,
92
95
  setup_cmd=setup_cmd,
93
96
  setup_cmd_path=setup_cmd_path,
@@ -116,31 +119,31 @@ class DockerComposeStartTask(DockerComposeTask):
116
119
  should_show_working_directory=should_show_working_directory,
117
120
  )
118
121
 
119
- def get_cmd_script(self, *args: Any, **kwargs: Any) -> str:
120
- # setup
121
- setup_cmd = self._create_cmd_script(
122
- self._setup_cmd_path, self._setup_cmd, *args, **kwargs
122
+ def _get_execute_docker_compose_script(
123
+ self,
124
+ compose_cmd: JinjaTemplate,
125
+ compose_options: Mapping[JinjaTemplate, JinjaTemplate],
126
+ compose_flags: Iterable[JinjaTemplate],
127
+ compose_args: Iterable[JinjaTemplate],
128
+ *args: Any
129
+ ) -> JinjaTemplate:
130
+ return "\n".join(
131
+ [
132
+ # compose start
133
+ super()._get_execute_docker_compose_script(
134
+ compose_cmd=compose_cmd,
135
+ compose_options=compose_options,
136
+ compose_flags=list(compose_flags) + ["-d"],
137
+ compose_args=compose_args,
138
+ *args,
139
+ ),
140
+ # compose log
141
+ super()._get_execute_docker_compose_script(
142
+ compose_cmd="logs",
143
+ compose_options={},
144
+ compose_flags=["-f"],
145
+ compose_args=[],
146
+ *args,
147
+ ),
148
+ ]
123
149
  )
124
- cmd_list = [setup_cmd] if setup_cmd.strip() != "" else []
125
- # compose command
126
- cmd_list = cmd_list + [
127
- # compose start
128
- self._get_docker_compose_cmd_script(
129
- compose_cmd="up",
130
- compose_options=self._compose_options,
131
- compose_flags=list(self._compose_flags) + ["-d"],
132
- compose_args=self._compose_args,
133
- *args,
134
- ),
135
- # compose log
136
- self._get_docker_compose_cmd_script(
137
- compose_cmd="logs",
138
- compose_options={},
139
- compose_flags=["-f"],
140
- compose_args=[],
141
- *args,
142
- ),
143
- ]
144
- cmd_str = "\n".join(cmd_list)
145
- self.log_info(f"Command: {cmd_str}")
146
- return cmd_str
@@ -3,7 +3,6 @@ import pathlib
3
3
  from collections.abc import Callable, Iterable, Mapping
4
4
  from typing import Any, Optional, TypeVar, Union
5
5
 
6
- from zrb.config.config import CONTAINER_BACKEND
7
6
  from zrb.helper.accessories.color import colored
8
7
  from zrb.helper.accessories.name import get_random_name
9
8
  from zrb.helper.docker_compose.fetch_external_env import fetch_compose_file_env_map
@@ -33,32 +32,22 @@ from zrb.task_input.any_input import AnyInput
33
32
 
34
33
  logger.debug(colored("Loading zrb.task.docker_compose_task", attrs=["dark"]))
35
34
 
35
+ TDockerComposeTask = TypeVar("TDockerComposeTask", bound="DockerComposeTask")
36
36
 
37
- def _get_ensure_zrb_network_task(backend: str):
38
- CURRENT_DIR = os.path.dirname(__file__)
39
- SHELL_SCRIPT_DIR = os.path.join(CURRENT_DIR, "..", "shell-scripts")
40
- ensure_container_backend = CmdTask(
41
- name="ensure-compose-backend",
42
- cmd_path=[
43
- os.path.join(SHELL_SCRIPT_DIR, "_common-util.sh"),
44
- os.path.join(SHELL_SCRIPT_DIR, f"ensure-{backend}-is-installed.sh"),
45
- ],
46
- preexec_fn=None,
47
- should_print_cmd_result=False,
48
- )
49
- return CmdTask(
50
- name="ensure-zrb-network",
51
- cmd=[
52
- f"{backend} network inspect zrb >/dev/null 2>&1 || \\",
53
- f"{backend} network create -d bridge zrb",
54
- ],
55
- upstreams=[ensure_container_backend],
56
- should_print_cmd_result=False,
57
- )
58
-
37
+ CURRENT_DIR = os.path.dirname(__file__)
38
+ SHELL_SCRIPT_DIR = os.path.join(CURRENT_DIR, "..", "shell-scripts")
59
39
 
60
- TDockerComposeTask = TypeVar("TDockerComposeTask", bound="DockerComposeTask")
61
- ensure_zrb_network_task = _get_ensure_zrb_network_task(CONTAINER_BACKEND)
40
+ ensure_container_backend = CmdTask(
41
+ name="ensure-compose-backend",
42
+ cmd_path=[
43
+ os.path.join(SHELL_SCRIPT_DIR, "_common-util.sh"),
44
+ os.path.join(SHELL_SCRIPT_DIR, "ensure-docker-is-installed.sh"),
45
+ ],
46
+ preexec_fn=None,
47
+ should_print_cmd_result=False,
48
+ should_show_cmd=False,
49
+ should_show_working_directory=False,
50
+ )
62
51
 
63
52
 
64
53
  @typechecked
@@ -93,6 +82,7 @@ class DockerComposeTask(CmdTask):
93
82
  compose_options: Mapping[JinjaTemplate, JinjaTemplate] = {},
94
83
  compose_flags: Iterable[JinjaTemplate] = [],
95
84
  compose_args: Iterable[JinjaTemplate] = [],
85
+ compose_profiles: CmdVal = "",
96
86
  compose_env_prefix: str = "",
97
87
  setup_cmd: CmdVal = "",
98
88
  setup_cmd_path: CmdVal = "",
@@ -120,21 +110,20 @@ class DockerComposeTask(CmdTask):
120
110
  should_show_cmd: bool = True,
121
111
  should_show_working_directory: bool = True,
122
112
  ):
123
- combined_env_files = list(env_files)
124
113
  CmdTask.__init__(
125
114
  self,
126
115
  name=name,
127
116
  group=group,
128
117
  inputs=inputs,
129
118
  envs=envs,
130
- env_files=combined_env_files,
119
+ env_files=env_files,
131
120
  icon=icon,
132
121
  color=color,
133
122
  description=description,
134
123
  executable=executable,
135
124
  cwd=cwd,
136
125
  should_render_cwd=should_render_cwd,
137
- upstreams=[ensure_zrb_network_task] + upstreams,
126
+ upstreams=[ensure_container_backend] + upstreams,
138
127
  fallbacks=fallbacks,
139
128
  on_triggered=on_triggered,
140
129
  on_waiting=on_waiting,
@@ -168,6 +157,7 @@ class DockerComposeTask(CmdTask):
168
157
  self._compose_flags = compose_flags
169
158
  self._compose_args = compose_args
170
159
  self._compose_env_prefix = compose_env_prefix
160
+ self._compose_profiles = compose_profiles
171
161
  self._compose_template_file = self.__get_compose_template_file(compose_file)
172
162
  self._compose_runtime_file = self.__get_compose_runtime_file(
173
163
  self._compose_template_file
@@ -333,14 +323,24 @@ class DockerComposeTask(CmdTask):
333
323
  raise Exception(f"Invalid compose file: {compose_file}")
334
324
 
335
325
  def get_cmd_script(self, *args: Any, **kwargs: Any) -> str:
326
+ cmd_list = []
327
+ # create network
328
+ create_network_script = self._get_create_compose_network_script()
329
+ if create_network_script.strip() != "":
330
+ cmd_list.append(create_network_script)
331
+ # set compose profiles
332
+ compose_profile_script = self._get_compose_profile_script(*args, **kwargs)
333
+ if compose_profile_script.strip() != "":
334
+ cmd_list.append(compose_profile_script)
336
335
  # setup
337
- setup_cmd = self._create_cmd_script(
336
+ setup_script = self._create_cmd_script(
338
337
  self._setup_cmd_path, self._setup_cmd, *args, **kwargs
339
338
  )
340
- cmd_list = [setup_cmd] if setup_cmd.strip() != "" else []
339
+ if setup_script.strip() != "":
340
+ cmd_list.append(setup_script)
341
341
  # compose command
342
342
  cmd_list.append(
343
- self._get_docker_compose_cmd_script(
343
+ self._get_execute_docker_compose_script(
344
344
  compose_cmd=self._compose_cmd,
345
345
  compose_options=self._compose_options,
346
346
  compose_flags=self._compose_flags,
@@ -352,7 +352,38 @@ class DockerComposeTask(CmdTask):
352
352
  self.log_info(f"Command: {cmd_str}")
353
353
  return cmd_str
354
354
 
355
- def _get_docker_compose_cmd_script(
355
+ def _get_compose_profile_script(self, *args, **kwargs) -> str:
356
+ # Get list representation of self._compose_profiles
357
+ compose_profiles = self._compose_profiles
358
+ if callable(compose_profiles):
359
+ compose_profiles = self._compose_profiles(*args, **kwargs)
360
+ if isinstance(compose_profiles, str):
361
+ compose_profiles = compose_profiles.split(",")
362
+ # Get only non empty profiles
363
+ filtered_compose_profiles = [
364
+ self.render_str(profile)
365
+ for profile in compose_profiles
366
+ if self.render_str(profile).strip() != ""
367
+ ]
368
+ if len(filtered_compose_profiles) == 0:
369
+ return ""
370
+ compose_profiles_str = ",".join(filtered_compose_profiles)
371
+ return f"export COMPOSE_PROFILES={compose_profiles_str}"
372
+
373
+ def _get_create_compose_network_script(self) -> str:
374
+ compose_data = read_compose_file(self._compose_runtime_file)
375
+ networks: Mapping[str, Mapping[str, Any]] = compose_data.get("networks", {})
376
+ scripts = []
377
+ for key, config in networks.items():
378
+ if not config.get("external", False):
379
+ continue
380
+ network_name = config.get("name", key)
381
+ scripts.append(
382
+ f"docker network inspect {network_name} > /dev/null 2>&1 || docker network create -d bridge{network_name}" # noqa
383
+ )
384
+ return "\n".join(scripts)
385
+
386
+ def _get_execute_docker_compose_script(
356
387
  self,
357
388
  compose_cmd: str,
358
389
  compose_options: Mapping[JinjaTemplate, JinjaTemplate],
@@ -383,4 +414,4 @@ class DockerComposeTask(CmdTask):
383
414
  if self.render_str(arg) != ""
384
415
  ]
385
416
  )
386
- return f"{CONTAINER_BACKEND} compose {options} {compose_cmd} {flags} {args}"
417
+ return f"docker compose {options} {compose_cmd} {flags} {args}"
zrb/task/flow_task.py CHANGED
@@ -85,7 +85,6 @@ class FlowTask(BaseTask):
85
85
  retry_interval=retry_interval,
86
86
  should_execute=should_execute,
87
87
  return_upstream_result=return_upstream_result,
88
- run=lambda *args, **kwargs: kwargs.get("_task").print_out("🆗"),
89
88
  )
90
89
 
91
90
  def copy(self) -> TFlowTask:
zrb/task/looper.py CHANGED
@@ -1,4 +1,4 @@
1
- from collections.abc import Callable
1
+ from collections.abc import Callable, Mapping
2
2
  from typing import Optional
3
3
 
4
4
  from zrb.helper.accessories.color import colored
@@ -6,6 +6,7 @@ from typing import Any, Optional, Union
6
6
  from zrb.helper.accessories.color import colored
7
7
  from zrb.helper.log import logger
8
8
  from zrb.helper.typecheck import typechecked
9
+ from zrb.helper.typing import JinjaTemplate
9
10
  from zrb.task.any_task import AnyTask
10
11
  from zrb.task.any_task_event_handler import (
11
12
  OnFailed,
@@ -16,9 +17,8 @@ from zrb.task.any_task_event_handler import (
16
17
  OnTriggered,
17
18
  OnWaiting,
18
19
  )
19
- from zrb.task.base_remote_cmd_task import BaseRemoteCmdTask, RemoteConfig
20
20
  from zrb.task.cmd_task import CmdTask, CmdVal
21
- from zrb.task_env.env import Env
21
+ from zrb.task_env.env import Env, PrivateEnv
22
22
  from zrb.task_env.env_file import EnvFile
23
23
  from zrb.task_group.group import Group
24
24
  from zrb.task_input.any_input import AnyInput
@@ -27,8 +27,6 @@ logger.debug(colored("Loading zrb.task.remote_cmd_task", attrs=["dark"]))
27
27
 
28
28
  _CURRENT_DIR = os.path.dirname(__file__)
29
29
  _SHELL_SCRIPT_DIR = os.path.join(_CURRENT_DIR, "..", "shell-scripts")
30
- with open(os.path.join(_SHELL_SCRIPT_DIR, "ssh-util.sh")) as file:
31
- _SSH_UTIL_SCRIPT = file.read()
32
30
 
33
31
  ensure_ssh_is_installed = CmdTask(
34
32
  name="ensure-ssh-is-installed",
@@ -37,15 +35,17 @@ ensure_ssh_is_installed = CmdTask(
37
35
  os.path.join(_SHELL_SCRIPT_DIR, "ensure-ssh-is-installed.sh"),
38
36
  ],
39
37
  preexec_fn=None,
38
+ should_print_cmd_result=False,
39
+ should_show_cmd=False,
40
+ should_show_working_directory=False,
40
41
  )
41
42
 
42
43
 
43
44
  @typechecked
44
- class RemoteCmdTask(BaseRemoteCmdTask):
45
+ class RemoteCmdTask(CmdTask):
45
46
  def __init__(
46
47
  self,
47
48
  name: str,
48
- remote_configs: Iterable[RemoteConfig],
49
49
  group: Optional[Group] = None,
50
50
  inputs: Iterable[AnyInput] = [],
51
51
  envs: Iterable[Env] = [],
@@ -54,9 +54,15 @@ class RemoteCmdTask(BaseRemoteCmdTask):
54
54
  color: Optional[str] = None,
55
55
  description: str = "",
56
56
  executable: Optional[str] = None,
57
+ remote_host: JinjaTemplate = "localhost",
58
+ remote_port: Union[JinjaTemplate, int] = 22,
59
+ remote_user: JinjaTemplate = "root",
60
+ remote_password: JinjaTemplate = "",
61
+ remote_ssh_key: JinjaTemplate = "",
57
62
  cmd: CmdVal = "",
58
63
  cmd_path: CmdVal = "",
59
64
  cwd: Optional[Union[str, pathlib.Path]] = None,
65
+ should_render_cwd: bool = True,
60
66
  upstreams: Iterable[AnyTask] = [],
61
67
  fallbacks: Iterable[AnyTask] = [],
62
68
  on_triggered: Optional[OnTriggered] = None,
@@ -74,31 +80,26 @@ class RemoteCmdTask(BaseRemoteCmdTask):
74
80
  max_error_line: int = 1000,
75
81
  preexec_fn: Optional[Callable[[], Any]] = os.setsid,
76
82
  should_execute: Union[bool, str, Callable[..., bool]] = True,
83
+ return_upstream_result: bool = False,
84
+ should_print_cmd_result: bool = True,
85
+ should_show_cmd: bool = True,
86
+ should_show_working_directory: bool = True,
77
87
  ):
78
- pre_cmd = "\n".join(
79
- [
80
- _SSH_UTIL_SCRIPT,
81
- "_SCRIPT=\"$(cat <<'ENDSCRIPT'",
82
- ]
83
- )
84
- post_cmd = "\n".join(["ENDSCRIPT", ')"', 'auth_ssh "$_SCRIPT"'])
85
- BaseRemoteCmdTask.__init__(
88
+ CmdTask.__init__(
86
89
  self,
87
90
  name=name,
88
- remote_configs=remote_configs,
89
91
  group=group,
90
92
  inputs=inputs,
91
- envs=envs,
93
+ envs=envs + [PrivateEnv(name="_ZRB_SSH_PASSWORD", default=remote_password)],
92
94
  env_files=env_files,
93
95
  icon=icon,
94
96
  color=color,
95
97
  description=description,
96
98
  executable=executable,
97
- pre_cmd=pre_cmd,
98
99
  cmd=cmd,
99
100
  cmd_path=cmd_path,
100
- post_cmd=post_cmd,
101
101
  cwd=cwd,
102
+ should_render_cwd=should_render_cwd,
102
103
  upstreams=[ensure_ssh_is_installed] + upstreams,
103
104
  fallbacks=fallbacks,
104
105
  on_triggered=on_triggered,
@@ -116,4 +117,40 @@ class RemoteCmdTask(BaseRemoteCmdTask):
116
117
  max_error_line=max_error_line,
117
118
  preexec_fn=preexec_fn,
118
119
  should_execute=should_execute,
120
+ return_upstream_result=return_upstream_result,
121
+ should_print_cmd_result=should_print_cmd_result,
122
+ should_show_cmd=should_show_cmd,
123
+ should_show_working_directory=should_show_working_directory,
124
+ )
125
+ self._remote_host = remote_host
126
+ self._remote_port = remote_port
127
+ self._remote_user = remote_user
128
+ self._remote_password = remote_password
129
+ self._remote_ssh_key = remote_ssh_key
130
+
131
+ def get_cmd_script(self, *args: Any, **kwargs: Any) -> str:
132
+ cmd_script = self._create_cmd_script(self._cmd_path, self._cmd, *args, **kwargs)
133
+ cmd_script = "\n".join(
134
+ [
135
+ "_SCRIPT=$(cat << 'ENDSCRIPT'",
136
+ cmd_script,
137
+ "ENDSCRIPT",
138
+ ")",
139
+ ]
119
140
  )
141
+ ssh_command = self._get_ssh_command()
142
+ return "\n".join([cmd_script, ssh_command])
143
+
144
+ def _get_ssh_command(self) -> str:
145
+ host = self.render_str(self._remote_host)
146
+ port = self.render_str(self._remote_port)
147
+ user = self.render_str(self._remote_user)
148
+ password = self.render_str(self._remote_password)
149
+ key = self.render_str(self._remote_ssh_key)
150
+ if key != "" and password != "":
151
+ return f'sshpass -p "$_ZRB_SSH_PASSWORD" ssh -t -p "{port}" -i "{key}" "{user}@{host}" "$_SCRIPT"' # noqa
152
+ if key != "":
153
+ return f'ssh -t -p "{port}" -i "{key}" "{user}@{host}" "$_SCRIPT"'
154
+ if password != "":
155
+ return f'sshpass -p "$_ZRB_SSH_PASSWORD" ssh -t -p "{port}" "{user}@{host}" "$_SCRIPT"' # noqa
156
+ return f'ssh -t -p "{port}" "{user}@{host}" "$_SCRIPT"'